Understanding Simple ActiveRecord Updates in Rails - ruby-on-rails

I'm just diving into ActiveRecord and have not been able to find an answer to my question. If I am updating an object's attributes and then calling save()... will ActiveRecord save to the DB ONLY when the new values are different from the old values?
Let's say I do something like this:
thing_to_update = Thing.find_or_create_by_code(some_code)
if thing_to_update.name != some_name
thing_to_update.update_attribute(:name, some_name)
end
I don't want to do extra calls to the db if I don't have to because I will potentially have to update a lot of objects. I tried to read through the docs and it doesn't mention anything about comparing new values with the old ones. Am I missing something here?
Thanks

Active Record didn't used to do partial SQL updates, but it has since April 2008.
What's new in Edge Rails: Partial Updates
ActiveRecord::Dirty documentation

ActiveRecord will not update your record if no attributes have changed. You can verify this yourself by calling thing_to_update.save from the console and observing the log. ActiveRecord will load the record, but it will not attempt to update it.

Related

Rails Console: Create a New Record Using an Existing Record

Recently I had to create a couple of records in a non-rails app database table based on a previous record. It got me thinking of how would I do this in a rails app. I tried a couple of things in the Console, but nothing works.
I want to do something like this:
001> user = User.new(User.first)
I know this doesn't work but hopefully it will show you what I an thinking. User is a large table/model, and I only need to change a few fields. So, if I can set up a new record with the same values in User.first, I can then edit the fields I need to before .save-ing the record.
Thanks for any help.
I think what you want is:
user = User.first.dup
user.assign_attributes(email: "myemail#test.test")
user.save
The first line uses dup to create a copy of the object. The copy is not yet saved to the database. Replace dup with clone if you're using an old version of Rails (<3.1).
In the second line, assign_attributes alters the attributes of the object, still without saving it to the database. If you were working with an object already saved in the database, you could use update instead of assign_attributes to change the attributes of the object and save the changes in one go. That won't work here, because we haven't saved our duplicate user yet. More details on that here.
The third line finally saves the new object to the database. It saves time to just do this once, at the end.

Rails - ActiveModel::Serializer virtual attribute error

I'm using the active_model_serializers gem for a RoR API. Versions:
Rails: 4.2.8
Ruby: 2.2.5
active_model_serializers: 0.10.0
I'm using a virtual attribute in a serializer. I get it by using a sub query when I retrieve the objects from the database.
You can find the code here: Github Gist
This is the error I'm getting:
undefined method 'number_of_reservations' for DiscountSchedule...
This field isn't defined in the table nor in the model (attr_accessor)
I'm not sure why it doesn't work, I have a very similar serializer and it's working OK.
Any help will be appreciated.
EDIT:
I have another serializer where the virtual/calculated field is working OK. My guess on this is that since AR is making a bunch of LEFT OUTER JOINS and the SELECT list of the query is very big at some point something is getting broke.
The link won't work for me as I don't have access at my work place, however, from the error I can recommend you to check if you have defined the attributes like this in your serializer attributes :number_of_reservations and have an action in the serializer that says
def number_of_reservations
// Your logic goes here.
end
I suspect this question has to be about ActiveRecord, rather than AMS. You're trying to use select and alias to collect some computed (aggregate) attribute along with objects themselves. This, unfortunately, won't work in ActiveRecord, at least not in versions below 4.2.X. And this is why you're observing this behavior, there is no number_of_reservations in your models.
To see what's going on, try to inspect #objects here: https://gist.github.com/LuisDeHaro/ebf92781b449aa1ee7b85f8f552dd672#file-some_controller-rb-L17
Indeed: the issue was by the big amount of LEFT JOINS that the includes(:table_name) is generating. The serializer then does not know what to do.
I found a monkey-patch gem that works for AR (Rails 4 & 5) that fix this.
https://github.com/alekseyl/rails_select_on_includes
So, the virtual field number_of_reservations is picked up by the serializer like a charm.
And, you might be wondering: why do you want to retrieve a field that is not in the table definition in the database. A: well, in some scenarios you will need a calculated field for EVERY row you are retrieving. A SQL sub query is one of the most efficient ways to do so.
It's working now for me.

Which callback does ActiveRecord use to record timestamp?

I'm just wondering here whether any of you guys know when ActiveRecord use it's "magic" to record the timestamp (e.g. created_at, updated_at).
What i mean when is, at which callback ? (if AR use callback at all).
I'm asking this because I want to create an auto-updating column (that record sequential number for each object) and I want to replicate AR way to do this as much as possible.
EDITED:
It seems that AR does it between after_validation and before_create/before_update. You can do some tests for this by creating a presence validation for created_at column and inserting new record with blank created_at, it would return an error.
I don't know where AR does it, but the proper place for what you describe sounds like before_create
In Rails 3.2.12, this code is located in lib/active_record/timestamp.rb.
As you mention in your question and DGM suggests, Rails will update the timestamps when creating or updating, so sticking your code in before_create and before_update should work.
You may also want to take a look at the ActiveRecord counter_cache functionality. ActiveRecord supports creation of a column that can automatically be incremented/decremented. Additionally, you can perform more complicated logic.

Using ar-extensions' import: synchronise doesn't work

I am using AR-Extensions to import a large number of objects to db, but synching them back from DB just isn't working.
MY code:
posts = [Post.new(:name=>"kuku1"), Post.new(:name=>"kuku2"), ...]
Post.import posts, :synchronize=>posts
posts are submitted to db, and each one is allocated with primary key (id) automatically. But when afterwards checking the objects in posts array, I see that they don't have id field, and new_record flag is still true.
I also tried adding :reload=>true, but that doesn't help as well.
Any idea why synch doesn't work?
This is not possible right now with new records. As of ar-extensions 0.9.3 this will not work when synchronizing new records as synchronizing expects the records you're sync'ing to already exist. It uses the primary key under the covers to determine what to load (but with new records the primary key is nil). This limitation* also exists in activerecord-import 0.2.5. If you can synchronize on other conditions I'd be happy to release a new gem allowing conditions to be passed in. For Rails 3.x you need to use activerecord-import though (it replaces ar-extensions). Please create ticket/issue on github: https://github.com/zdennis/activerecord-import/issues
For Rails 2.x you still want to use ar-extensions, and I'd likely backport the activerecord-import update and push out a new gem as well. If you'd like this functionality here please create a ticket/issue on github: https://github.com/zdennis/ar-extensions/
Patches are welcome as well.
*The limitation here is a database constraint, as its impossible to get the ids of all newly created records after a single insert/import without doing something strange like table locking, which I don't think is a good solution to that problem. If anyone has ideas I'm all ears.
UPDATE
activerecord-import 0.2.6 and ar-extensions 0.9.4 have been released and includes support for specifying the fields you want to synchronize on. Those fields should be unique. See http://www.continuousthinking.com/2011/4/6/activerecord-import-0-2-6-and-ar-extensions-0-9-4

Calling ActiveRecord's #relationship_ids = [1,2,3] saves immediately. Any workarounds?

I've come across an oddity in ActiveRecord's #relationship_ids method (that's added automatically when you declare 'has_many'), which saves immediately for existing records, which is causing me some issues, and I wonder if anyone had any useful advice.
I'm running Rails 2.3.5.
Consider this simple scenario, where an article has_many tags, say:
a = Article.first
a.name = "New Name" # No save yet
a.author_id = 1 # No save yet
a.tag_ids = [1,2,3] # These changes are saved to the database
# immediately, even if I don't subsequently
# call 'a.save'
This seems surprising to me. It's specifically causing problems whilst trying to build a preview facility - I want to update a bunch of attributes and then preview the article without saving it - but in this instance the tag changes do get saved, even though no other fields do.
(Of possible relevance is that if 'a' is a new article, rather than an existing one, things behave as I'd expect - nothing is saved until I call 'a.save')
I have a fairly nasty workaround - I can override the tag_ids= method in my model to instead populate an instance variable, and actually save the related models in a before_save callback.
But I'd love to know of a simpler way than me having to do this for every model with a has_many relationship I'd like to create a preview facility for.
Does anyone have any fixes/workarounds/general advice? Thanks!
There's a reason things are this way. It's called foreign keys. In a has many relationship, the information that links to the model that has many is stored outside of that model as a foreign key.
As in Articles, has many tags. The information that links a tag to an article is stored either in the tags table or in a join table. When you call save on an article you're only saving the article.
Active record modifies those other records immediately. Except in the case where you're working with a new article that hasn't been saved yet. Rails will delay creating/updating the associated records if it doesn't know which id to place in the foreign key.
However, if you're modifying existing records, the solution you've decided on is really all that you can do. There's an even uglier hack using accepts_nested_attributes_for, but it's really not worth the effort.
If you're looking to add this behaviour to many models but not all models, you might want to consider writing a simple plugin to redefine the assigment the method you need and add the call back in a single class method call. Have a look at the source of something like acts_as_audited to see how it's done.
If you're looking to add this behaviour to all models, you can probably write a wrapper for has_many to do that.

Resources