Search code examples
ruby-on-railsruby-on-rails-3activerecorddynamic-finders

find_or_create_by in Rails 3 and updating for creating records


I'm not sure if I should be updating records this way or if I'm missing something.

I have a table with 5 columns (not including timestamps and id) 3 of which are distinct, and 2 which will get updated. The 3 distinct which I will find or create by are room_id, date, and source. The other 2 are price and spots available (these change hourly, daily etc.)

My question is, should I first find or create the record, then update (or create) the price and spots or can I do it all at once? You can see the two ways I'm doing it now, and I'm not sure if its actually doing what I'm expecting.

Also, is there any downside to do a find_and_create_by like this?

Thanks

  private

  def self.parse_data(params,data)
    data.beds.each do |bed|
      room = Room.find_or_create_room(bed.title, params[:id])

      #find clones somehow
      #puts bed.nights.first.price
      bed.nights.each_with_index do |night,index|
        available = Available.find_or_create_by_room_id_and_bookdate_and_source(
          :room_id => room.id, 
          :bookdate => (params[:date].to_date)+index, 
          :source => data.class.to_s#,
          #:price => night.price
        )
        #available.price = night.price
        #available.spots = night.spots
        #available.save
      end

    end

Solution

  • Here is two approaches.

    First you can extend Available with exact method you need:

    def self.find_or_create_by_room_id_and_bookdate_and_source(room_id, bookdate, source, &block)
      obj = self.find_by_room_id_and_bookdate_and_source( room_id, bookdate, source ) || self.new(:room_id => room_id, :bookdate => bookdate, :source => source)
      yield obj
      obj.save
    end
    

    usage

    Available.find_or_create_by_room_id_and_bookdate_and_source(room.id, (params[:date].to_date)+index, data.class.to_s) do |c|
      c.price = night.price
      c.spots = night.spots
    end
    

    This is awkward. So for being more flexible you can create update_or_create_by... method for ActiveRecord using method_missing magic:

    class ActiveRecord::Base
      def self.method_missing(method_id, *args, &block)
        method_name = method_id.to_s
        if method_name =~ /^update_or_create_by_(.+)$/
          update_or_create($1, *args, &block)
        else
          super
        end
      end
      def self.update_or_create(search, *args, &block)
        parameters = search.split("_and_")
        params = Hash[ parameters.zip(args) ]
        obj = where(params).first || self.new(params)
        yield obj
        obj.save
        obj
      end
    end
    

    So now you can use it:

    Available.update_or_create_by_id_and_source(20, "my_source") do |a|
      a.whatever = "coooool"
    end