Update attributes without touching the database in Rails - ruby-on-rails

I have a situation where I have an ActiveRecord::Relation object, where the relation's objects have some has_many associations. There is another piece of code that deletes the database rows associated with this relation that doesn't go through the relation, using delete_all. I know what the new state of the relation's associations is even without going to the database so I want to be able to set the object's attributes in this relation manually without touching the database again.
I found this article which mentions the write_attribute method. This works, but it looks like it has been deprecated, so I'd rather not use. It also mentions attributes= as a way of doing this without accessing the database. Is there something that can achieve the effect of write_attribute where I won't access the database when modifying a relation's attributes?

assign_attributes
It's like update_attributes, without saving.

Related

access ActiveRecord Has Many associations

I have a model which has a lot of associations. What I need to do is to check whether one of those associations is not present or all of them are set correctly.
To do so, I created an array which includes all of those needs-to-be-checked fields. And created a loop through each element, but the case is that I can not access has_many related attributes with read_attribute method. It basically returns nil whenever I try to access has many associated fields.
What I am trying to do, I can access all related objects via car.drivers but I can not access the same object with car.read_attribute(:drivers) (because some of them are attributes and some are relations)
I think it's the behavior of read_attribute, so what should I use to access any (attribute or relation) on ActiveRecord object?
Regarding to the comments, it looks like no one understand what I am trying to do. I want to access the relations of one ActiveRecord object such like;
RELATIONS.each do |relation|
puts "#{relation} exists" if #object.relation.present?
end
What I do not know about this, is there any method that I can access the related objects with their string typed name. Similar to, #object.read_attribute(:attribute_name) In that way I can create a for loop, but for relations not for the attributes
To do so, I used dynamical method call. Below is an example of showing it
RELATIONS.each do |relation|
puts "#{relation} exists" unless #object.send('relation').nil?
end

Handling associations w/ null objects in Rails

I'm using the Null Object pattern in my Rails application to implement the concept of a guest user account.
Like many apps, I have a method on ApplicationController called current_user.
In the case of a non-logged in user, I want to use my guest user null object.
It works in many cases, but then there run into something like the following -
params.merge({ user: current_user })
MyModel.new(params)
Of course this fails, with the following exception.
ActiveRecord::AssociationTypeMismatch: User expected, got GuestUser
My question is, what is a way to elegantly handle this kind of case. The idea for the Null Object pattern is that you can transparently swap in this null object and have it essentially be a duck type of the real object.
It's obvious how to do that for methods being called on the object, but in this case, I want to be able to pass this in and basically have it set the association column to null, rather than needing a whole bunch of custom logic (avoiding that is the whole point of the null object pattern anyway).
A polymorphic relation isn't quite it.
Quick answer: No such thing as an elegant way to handle that (I'm not sure how elegance is quantified).
You'll have to create a concern that mimics the persistence methods of the model from which your null object is based on (User). You'll also have to write methods to appease ActiveRecord to make the associated column be nil.
Fortunately for you, this use-case has been solved
if your MyModel accepts null for user_id, then you can do
params.merge(user: current_user) unless current_user.is_a?(GuestUser)
MyModel.new(params)
Using the null object pattern here is definatly not a good idea since you need database generated ids to build associations and maintain referential integrity if you intend the user to have any kind of persistence before "registering".
Allowing a MyModel to be created without a user_id would essentially create an orphaned record and just gives you another problem of linking it to user behind the screen. Thats why your schema should not allow it in the first place.
Rather you want to create the guest user record when needed (like when a guest user adds the first item to a cart) and use a recurring task (like a Cron tab) to periodicaly clean out junk records.
I would also consider if you really want to setup guest users as a seperate class since STI and polymorphism tends to get really messy when joining. Just use a timestamp column (records when the account was activated) or an enum instead.
One option would be to override the user= method, so that it's aware of the existence of GuestUser (and can handle appropriately):
def user=(value)
if value.is_a?(GuestUser)
super(nil)
else
super
end
end
All mass-assignment methods in Rails (create, update, etc.) will use the appropriate setter to set the value. This can be easily be put into a concern if this is a common pattern in your application.
If you don't allow nil in the user_id column you have the flexibility to do something like assign a sentinel value, which you could then use in your accessor as well:
def user
if user_id == GUEST_USER_ID
GuestUser.new
else
super
end
end
I had a similar problem. I just went from assigning the object to assigning the object.id which I set to nil on the Null Object. It is kind of a hack I think though.

Saving record fails due to uniqueness conflict with itself?

I have a procedure which receives two models, one which already exists, and another one which holds new attributes which I want to merge in the first one.
Since other parts of the program are holding the same reference to the new model, I can't just operate on the existing one. Therefor I do the following:
def merge(new_model, existing_model)
new_model.attributes = existing_model.attributes.merge(new_model.attributes)
new_model.id = existing_model.id
end
Now the new_model is being saved which gives me the uniqueness erorr (even though it's technically the same model). I also tried using the reload method, but that yields the same result.
Background:
The method above is run in a before_add callback on an association. I want to be able to call update on a model (with nested associations) without having to specify IDs of the nested models. This update is supposed to merge some associations, which is why I try to do the whole merge thing above.
You can't set the id of a model and then save the record expecting the id to be set since the id is the primary key of the database. So you are actually creating a whole new record and, thus, the uniqueness validation error. So you'll need to think of some other design to accomplish what you are wanting. It may help to know that what you are trying to do sounds similar to a deep_dup, except that ActiveRecord doesn't define this method (but Hash does).

How can I get a list of the associated models that were updated during the save of an instance of ActiveRecord?

I'm passing params to a model instance and saving it with update_attributes. It is associated with several other models and I've configured it to update some of these with accepts_nested_attributes_for.
This is very nice and clean as I only have to update the one model, but I'd like to get a list of the associated(nested) models that were also updated so that I can give the user feedback about some of the fields that have changed.
Is there a way to do this, or am I approaching the problem in the wrong way?
I've found a solution to my question, maybe not the best one but it will work.
For a list of models that are associated and have accepts_nested_attributes_for configured we go:
associations = ModelClass.reflect_on_all_autosave_associations()
Each of these association objects has a name attribute(the association name), which can be used to access the association on the instance, and then we can check whether this association has changed:
associations.each{|assoc|
model_instance.send(assoc.name).changed?
}
It should be noted that we cannot use update_attributes with this solution, as all the models are saved before we can check whether anything has changed. So we have to assign_attributes and save the model in separate steps:
model_instance.assign_attributes(params[:model_instance])
// check for changes on associations here
model_instance.save()

Prevent sql update on existing Rails model when appending to has_many relationship

According to the present Rails documentation, regarding the << operator on a has_many relationship on an existing object:
collection<<(object, …)
Adds one or more objects to the collection by setting their foreign keys
to the collection’s primary key.
(This is the interesting bit)
Note that this operation instantly fires update sql without waiting for
the save or update call on the parent object.
I didn't realize this would happen, I was quite surprised; I could have sworn this was not the case in the past, though I admit I could be wrong.
In either case, I haven't been able to find any additional documentation regarding this, however I wonder if there is a way to prevent this update?
My situation is simple, I merely have an object which exists in the database, which is being prepared for an "edit" page. I append one or multiple related objects before the page is render. C'est tout.
Update:
Apparently the same update-scenario also occurs if you set the has_many relationship directly from an array using the = operator.
Use the collection's build method. That won't immediately fire a SQL statement like the others do.
foo.bars.build(attributes)
foo.save
Lots of good information can be found here: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Note: This method assumes you have the flexibility to create your objects through the build method rather than using Bar.new.

Resources