ActiveRecord partial updates
Published over 5 years ago
OMFG! This is the moment ya all have been waiting for..ActiveRecord partial updates are now possible !! It’ll make your application run 100x faster!!
ActiveRecord updates all the columns when you save the object, without bothering to see if the column was changed or not.
Notice the UPDATE statement in the following console session :
>> p = Person.find :first Person Load (0.002784) SELECT * FROM people LIMIT 1 => #<Person id: 1, name: "Pratik", address: "Shangri-la", history: "pff", created_at: "2007-12-18 05:07:53", updated_at: "2007-12-18 06:08:13"> >> p.save Person Update (0.001178) UPDATE people SET "created_at" = '2007-12-18 05:07:53', "name" = 'Pratik', "history" = 'pff', "address" = 'Shangri-la', "updated_at" = '2007-12-18 06:20:11' WHERE "id" = 1 => true

module ActiveRecord module Changed def self.included(base) base.alias_method_chain :write_attribute, :changed base.alias_method_chain :update_without_timestamps, :changed base.alias_method_chain :save, :changed base.alias_method_chain :save!, :changed end private def write_attribute_with_changed(attr_name, value) # If you're accessing attr= method, you should change the value ;-) changed_attributes << attr_name.to_s write_attribute_without_changed(attr_name, value) end def update_without_timestamps_with_changed quoted_attributes = attributes_with_quotes(false, false) quoted_attributes.reject! { |key, value| !changed_attributes.include?(key.to_s)} return 0 if quoted_attributes.empty? connection.update( "UPDATE #{self.class.quoted_table_name} " + "SET #{quoted_comma_pair_list(connection, quoted_attributes)} " + "WHERE #{connection.quote_column_name(self.class.primary_key)} " + "= #{quote_value(id)}", "#{self.class.name} Update" ) end def changed_attributes @changed_attributes ||= Set.new end def save_with_changed save_without_changed ensure changed_attributes.clear end def save_with_changed! save_without_changed! ensure changed_attributes.clear end end end ActiveRecord::Base.send :include, ActiveRecord::Changed
Ok, you won’t too much of performance boost with this. I lied. It was a joke. Get over it.
Using this code :
>> p = Person.find :first Person Load (0.000538) SELECT * FROM people LIMIT 1 => #<Person id: 1, name: "Hellll", address: "whatever", history: "pff", created_at: "2007-12-18 05:07:53", updated_at: "2007-12-18 05:40:45"> >> p.save Person Update (0.000536) UPDATE people SET "updated_at" = '2007-12-18 06:07:55' WHERE "id" = 1 => true >> p.name = "Pratik" => "Pratik" >> p.save Person Update (0.000908) UPDATE people SET "name" = 'Pratik', "updated_at" = '2007-12-18 06:08:04' WHERE "id" = 1 => true >> p.address = "Shangri-la" => "Shangri-la" >> p.save Person Update (0.000542) UPDATE people SET "address" = 'Shangri-la', "updated_at" = '2007-12-18 06:08:13' WHERE "id" = 1 => true
Yes, there is a big fat but here
Do partial updates make sense ?
class Whatever < <Something>::Base def validate errors.add("Invalid") if self.foo == "Hello" and self.bar == "World" end end Current Record State : { :foo => "abc", :bar => "xyz" } ( Two processes fetch the same record concurrently ) t0 : Process 1 : Fetch the record | Process 2 : Fetch the record t1 : Process 1 : set 'foo' to "Hello". keep 'bar' as "xyz" { :foo => "Hello", :bar => "xyz" } Partial update will execute the query only to update :foo t2 : Process 2 : set 'bar' to "World". keep 'bar' as "abc" { :foo => "abc", :bar => "World" } Partial update will execute the query only to update :bar t3 : Final state of the record : { :foo => "Hello", :bar => "World" }
Yes, it can leave your records in invalid state
Awww…Did I make you sad :-( ?

Well, don’t worry. The point is :
class Whatever < <Something>::Base lazy_attributes :some_column_which_is_huge_and_rarely_changes end
And apply the partial update logic only to the attributes supplied to lazy_attributes