Search code examples
ruby-on-railsvirtual-attribute

Rails: Find or create by virtual attribute


I have kind of a tricky problem I'm working on in my current rails app. In my app users share photos. Photos can be associated with a city, so City has_many :photos. I want users to be able to associate their photos with a city using autocomplete and a natural language syntax. Ie: New York, NY or Paris, France.

I'd like to do this with an autocomplete text box so that if the user types "Athens" they would see a list with:

Athens, Greece
Athens, GA

... and if the person actually wanted "Athens, TX" they could simply type that in and it would create a new city record.

My city model has the fields, name, state, country. State and Country are 2-letter postal codes (I use Carmen to validate them). I have a virtual attribute called full_name which returns "City, State Code" (like New York, NY) for North American cities, and "City, Country Name" (like Paris, France) for all others.

def full_name
    if north_american?
        [name, state].join(', ')
    else
        [name, Carmen.country_name( country )].join(', ')
    end
end

def north_american?
    ['US','CA'].include? country
end

My question is, to get the text field working, how can I create a find_or_create method that can accept a string with the city name and either state code or country name and find or create that record?


Update

Inspired by Kandada's answer I came up with something a little different:

def self.find_or_create_by_location_string( string )
  city,second = string.split(',').map(&:strip)
  if second.length == 2
    country = self.country_for_state( second )
    self.find_or_create_by_name_and_state( city, second.upcase, :country => country )
  else
    country = Carmen.country_code(second)
    self.find_or_create_by_name_and_country( city, country )
  end
end

def self.country_for_state( state )
  if Carmen.state_codes('US').include? state
    'US'
  elsif Carmen.state_codes('CA').include? state
    'CA'
  else
    nil
  end
end

This is rocking my specs right now, so I think my issue is solved.


Solution

  • class Photo < ActiveRecord::Base
    
      attr_accessor :location
    
      def self.location_hash location
        city,state,country = location.split(",")
        country = "US" if country.blank?
        {:city => city, :state => state,  :country => :country}
      end
    
    end
    

    Now you can 'find_or_create_by_*'

    Photo.find_or_create_by_name(
      Photo.location_hash(location).merge(:name => "foor bar")
    )