Ruby on Rails Triggers Update on Serialized Attribute Every Time
This is expected behavior. It is very difficult to detect changes within a serialized attribute, so they are updated on every save.
Consider the following (ruby 1.8.7) irb session:
ruby-1.8.7-p352 :001 > x = "--- \n:b: 2\n:a: 1\n"
=> "--- \n:b: 2\n:a: 1\n"
ruby-1.8.7-p352 :002 > y = "--- \n:a: 1\n:b: 2\n"
=> "--- \n:a: 1\n:b: 2\n"
ruby-1.8.7-p352 :003 > x == y
=> false
ruby-1.8.7-p352 :004 > YAML.load(x) == YAML.load(y)
=> true
Rails 3 only update serialized attribute when changed
Model with serialized columns being saved all the time is the expected behaviour according to this answer.
I haven't tried "store configurable gem" but it sounds like it should be smart enough to detect that.
Is there a way to prevent serialized attributes in rails from getting updated even if there are not changes?
Yes, that was bugging me too. This is what I did for Rails 2.3.14 (or lower):
# config/initializers/nopupdateserialize.rb
module ActiveRecord
class Base
class_attribute :no_serialize_update
self.no_serialize_update = false
end
end
module ActiveRecord2
module Dirty
def self.included(receiver)
receiver.alias_method_chain :update, :dirty2
end
private
def update_with_dirty2
if partial_updates?
if self.no_serialize_update
update_without_dirty(changed)
else
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
update_without_dirty
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecord2::Dirty
Then in your controller use:
model_item.no_serialize_update = true
model_item.update_attributes(params[:model_item])
model_item.increment!(:hits)
model_item.update_attribute(:nonserializedfield => "update me")
etc.
Or define it in your model if you do not expect any changes to the serialized field once created (but update_attribute(:serialized_field => "update me" still works!)
class Model < ActiveRecord::Base
serialize :serialized_field
def no_serialize_update
true
end
end
Fields_for build repeating itself every time I update
The point in your code is, when Photo.friendly.find(params[:id])
have some sponsor already created and then when you @photo.sponsors.build
, you are adding an empty sponsor, so, you need to build only when a Photo.friendly
has not a sponsor, I think this can help you
def edit
@photo = Photo.friendly.find(params[:id])
@photo.sponsors.build unless @photo.sponsors.any?
end
Is there a way to avoid automatically updating Rails timestamp fields?
Do this in a migration or in a rake task (or in the new database seeds if you're on edge rails):
ActiveRecord::Base.record_timestamps = false
begin
run_the_code_that_imports_the_data
ensure
ActiveRecord::Base.record_timestamps = true # don't forget to enable it again!
end
You can safely set created_at
and updated_at
manually, Rails won't complain.
Note:
This also works on individual models, e.g.
User.record_timestamps = false
Rails create or update magic?
Rails 6
Rails 6 added an upsert
and upsert_all
methods that deliver this functionality.
Model.upsert(column_name: value)
[upsert] It does not instantiate any models nor does it trigger Active Record callbacks or validations.
Rails 5, 4, and 3
Not if you are looking for an "upsert" (where the database executes an update or an insert statement in the same operation) type of statement. Out of the box, Rails and ActiveRecord have no such feature. You can use the upsert gem, however.
Otherwise, you can use: find_or_initialize_by
or find_or_create_by
, which offer similar functionality, albeit at the cost of an additional database hit, which, in most cases, is hardly an issue at all. So unless you have serious performance concerns, I would not use the gem.
For example, if no user is found with the name "Roger", a new user instance is instantiated with its name
set to "Roger".
user = User.where(name: "Roger").first_or_initialize
user.email = "email@example.com"
user.save
Alternatively, you can use find_or_initialize_by
.
user = User.find_or_initialize_by(name: "Roger")
In Rails 3.
user = User.find_or_initialize_by_name("Roger")
user.email = "email@example.com"
user.save
You can use a block, but the block only runs if the record is new.
User.where(name: "Roger").first_or_initialize do |user|
# this won't run if a user with name "Roger" is found
user.save
end
User.find_or_initialize_by(name: "Roger") do |user|
# this also won't run if a user with name "Roger" is found
user.save
end
If you want to use a block regardless of the record's persistence, use tap
on the result:
User.where(name: "Roger").first_or_initialize.tap do |user|
user.email = "email@example.com"
user.save
end
Searching serialized data, using active record
Basically, you can't. The downside of #serialize is that you're bypassing your database's native abstractions. You're pretty much limited to loading and saving the data.
That said, one very good way to slow your application to a crawl could be:
MyModel.all.select { |m| m.mycode.include? 43402 }
Moral of the story: don't use #serialize for any data you need to query on.
How to update_attributes without executing before_save?
In rails 3.1 you will use update_column.
Otherwise:
In general way, the most elegant way to bypass callbacks is the following:
class Message < ActiveRecord::Base
cattr_accessor :skip_callbacks
before_save lambda { foo(publisher); bar }, :unless => :skip_callbacks # let's say you do not want this callback to be triggered when you perform batch operations
end
Then, you can do:
Message.skip_callbacks = true # for multiple records
my_message.update_attributes(:created_at => ...)
Message.skip_callbacks = false # reset
Or, just for one record:
my_message.update_attributes(:created_at => ..., :skip_callbacks => true)
If you need it specifically for a Time
attribute, then touch
will do the trick as mentioned by @lucapette .
Related Topics
How to Use Ruby to Write Individual Records to a Redshift Database
How to Pass Named Arguments to a Rake Task
Rspec Load Time Incredible Long on Os X
Ruby Syntactic Sugar: Dealing with Nils
Ruby on Rails HTML-Table Generator
Disable Devise's :Confirmable On-The-Fly to Batch-Generate Users
Using Each_With_Index with Map
Use Same Controller for Multiple Routes
Heroku and @Font-Face - Embedded Fonts Wont Display on Heroku
How to Use Your Own MySQL Database Server with Heroku
Ruby JSON.Pretty_Generate ... Is Pretty Unpretty
Application.CSS Not Being Served as an Asset
Generate a 'Link_To' to the Controller Action 'Edit', Dynamically
Using Ruby 2.0 on Amazon Opsworks
Does Cucumber Do Away with The Need to Write Unit Tests