Search code examples
ruby-on-railsrubyactiverecordmodelruby-on-rails-5

How do I get back an ActiveRecord instead of an ActiveRecord relation?


Using Rails 5. How do I get an ActiveRecord instead of an ActiveRecord relation? I ahve the below model

class State < ActiveRecord::Base
  belongs_to :country

    ...
  def self.cached_find_us_state_by_name(name)
    Rails.cache.fetch("#{name.upcase} US") do
      find_us_state_by_name(name)
    end
  end

  # Look up a US state by its full name 
  def self.find_us_state_by_name(name)
    search_criteria = ["upper(states.name) = ? "]
    search_criteria.push( "states.country_id = countries.id " ) 
    search_criteria.push( "countries.iso = 'US' " ) 

    results = State.joins(:country).where( search_criteria.join(' and '),
                                           name.upcase) 
  end

but when I lookup an item using the methods, waht I get back is an ActiveReocrd relation ...

2.4.0 :004 >  State.cached_find_us_state_by_name('Ohio')
 => #<ActiveRecord::Relation [#<State id: 3538, name: "Ohio", country_id: 233, iso: "OH">]>

This is cauisng problems later on, specifically ...

ActiveRecord::AssociationTypeMismatch: State(#70288305660760) expected, got State::ActiveRecord_Relation(#70288290686360)
    from /Users/davea/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/associations/association.rb:221:in `raise_on_type_mismatch!'
    from /Users/davea/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/associations/belongs_to_association.rb:12:in `replace'

Edit: Per the suggestion given, I changed my methods to

  def self.cached_find_us_state_by_name(name)
    Rails.cache.fetch("#{name.upcase} US") do
      find_us_state_by_name(name)
    end
  end

  # Look up a US state by its full name 
  def self.find_us_state_by_name(name)
    search_criteria = ["upper(states.name) = ? "]
    search_criteria.push( "states.country_id = countries.id " ) 
    search_criteria.push( "countries.iso = 'US' " ) 

    results = State.joins(:country).where( search_criteria.join(' and '),
                                           name.upcase).take  
  end

but alas, I still get an ActiveRecord::Relation

2.4.0 :011 > State.cached_find_us_state_by_name('Ohio')
 => #<ActiveRecord::Relation [#<State id: 3538, name: "Ohio", country_id: 233, iso: "OH">]>

Solution

  • You can use find_by instead of where - the source code for this is simple:

    def find_by(*args)
      where(*args).take
    end
    

    Another way is to call .first or [0] but if your where has a lot of records this will be slow since it's loading them all into memory before selecting the first. If use limit(1) then these methods will be acceptably fast.