Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Override Save To Perform Selective Update

I am working on a Rails 3.2.8 application that uses a homegrown REST API to insert new data. The insertion logic is generic for every endpoint and results quite simply in a call to Model.save.

For one of the model types I would like to first check if a pre-existing record exists and if so, update as opposed to insert. If the code allowed me to interact at the Controller level this would be easy through the use of find_or_create_by, however (I think) my only option is to override the save method in the Model or use a before_save callback.

I'm struggling to figure out a way to make this work, as any calls to save or update_attributes inside the Model simply results in an infinite loop (for obvious reasons).

Is there a way to utilize either before_save or to override save in such a way that I can first check if a record exists with attributes x and y and if so retrieve that record and perform the update, otherwise move forward with a standard ActiveRecord save?

Here is my code as it currently stands inside the Activity model, which does not work due to the infinite loop issue:

def save
  a = UserActivity.find_or_initialize_by_user_id_and_activity_id(user_id: user_id,     activity_id: activity_id)
  a.update_attributes start_at: start_at, end_at: end_at.....
end
like image 377
JaySquat Avatar asked Nov 05 '12 19:11

JaySquat


1 Answers

You seem to be needing find_or_create_by_* method.

To avoid the loop, you should not place this in save method, but in one of these two places:

Option 1: Controller level

In your controller where you instanciate this UserActivity instance, you instead write:

a = UserActivity.find_or_create_by_user_id_and_activity_id(user_id: user_id, activity_id: activity_id)
a.update_attributes start_at: start_at, end_at: end_at.....

Option 2: Class method

If you find yourself adding the above code to several contrllers, a better way would be to define a new class method in UserActivity:

class UserActivity
  def self.create_or_update_from_attrs(user_id, activity_id, start_at, end_at...)
    a = UserActivity.find_or_create_by_user_id_and_activity_id(user_id: user_id,     activity_id: activity_id)
    a.update_attributes start_at: start_at, end_at: end_at.....
  end
end

And in the controllers, obviously:

UserActivity.create_or_update_from_attrs(...)

Override save

Of course, you can override the save method too, but this does duplicate Rails functionality (find_or_create_by...) and as such violates DRY and you could shoot yourself in your foot some time later when this conflicts with some other situation you run into, so I discourage the usage of this:

EDIT: updated to avoid infinite loop

class UserActivity
  def save
    # If this is new record, check for existing and update that instead:
    if new_record? && a = UserActivity.where(user_id: user_id, activity_id: activity_id).first
      a.update_attributes start_at: start_at, end_at: end_at ...
      return true # just to comply with Rails conventions          
    else
      # just call super to save this record
      super
    end
  end
end
like image 91
Laas Avatar answered Nov 03 '22 08:11

Laas