From e682ecda465f74e676f53eb3a8003e985b467edf Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 20 Dec 2007 14:08:50 +0000 Subject: [PATCH] Lazy attributes and partial updates --- CHANGELOG | 1 + README | 12 ++++++ init.rb | 1 + lib/activerecord/changed.rb | 52 +++++++++++++++++++++++++ test/app/models/person.rb | 1 + test/db/migrate/002_add_stuff_to_person.rb | 11 ++++++ test/db/schema.rb | 4 +- test/test/unit/person_test.rb | 62 ++++++++++++++++++++++++++++-- 8 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 lib/activerecord/changed.rb create mode 100644 test/db/migrate/002_add_stuff_to_person.rb diff --git a/CHANGELOG b/CHANGELOG index 7af86cd..11d160c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1 +1,2 @@ +v1. Added support for declarative lazy updates v0. Added argument-less version of respond_to diff --git a/README b/README index 31bb68d..8cd712d 100644 --- a/README +++ b/README @@ -14,6 +14,18 @@ respond_to do xml { render :xml => @article } end +* Lazy attributes to provide partial updates + +You can declare lazy attributes in your model definition : + +class Person < ActiveRecord::Base + lazy_attributes :history, :bio +end + +In this case, when you do @person.save, ActiveRecord will include bio and history columns in UPDATE query only if you accessed +@person.history= or @person.bio= methods. + + * Very common console tricks - [] finders for model so you can do Article[1], Article[:all] - "log_to" function to print out db queries to rails irb console diff --git a/init.rb b/init.rb index bde8a00..4d1f9f8 100644 --- a/init.rb +++ b/init.rb @@ -1,2 +1,3 @@ require 'railties/console' require 'actionpack/mime_responds_extension' +require 'activerecord/changed' diff --git a/lib/activerecord/changed.rb b/lib/activerecord/changed.rb new file mode 100644 index 0000000..309a2d9 --- /dev/null +++ b/lib/activerecord/changed.rb @@ -0,0 +1,52 @@ +module ActiveRecord + module Changed + def self.included(base) + base.extend ClassMethods + base.alias_method_chain :attributes_with_quotes, :changed + base.alias_method_chain :write_attribute, :changed + base.alias_method_chain :save, :changed + base.alias_method_chain :save!, :changed + base.alias_method_chain :reload, :changed + end + + module ClassMethods + def lazy_attributes(*args) + @@lazy_attributes ||= Array(args).map(&:to_s).to_set + end + end + + private + + def attributes_with_quotes_with_changed(include_primary_key = true, include_readonly_attributes = true) + original = attributes_with_quotes_without_changed(include_primary_key, include_readonly_attributes) + + # Reject only if both supplied arguments are false + original.reject! { |key, value| changed_lazy.include?(key) } if !(include_primary_key || include_readonly_attributes) + original + end + + def write_attribute_with_changed(attr_name, value) + changed_lazy.delete(attr_name.to_s) + write_attribute_without_changed(attr_name, value) + end + + def reload_with_changed + @changed_lazy = nil + reload_without_changed + end + + def changed_lazy + @changed_lazy ||= self.class.lazy_attributes.clone + end + + def save_with_changed + save_without_changed ensure @changed_lazy = nil + end + + def save_with_changed! + save_without_changed! ensure @changed_lazy = nil + end + end +end + +ActiveRecord::Base.send :include, ActiveRecord::Changed \ No newline at end of file diff --git a/test/app/models/person.rb b/test/app/models/person.rb index 76b6657..26f0003 100644 --- a/test/app/models/person.rb +++ b/test/app/models/person.rb @@ -1,3 +1,4 @@ class Person < ActiveRecord::Base + lazy_attributes :history, :bio validates_presence_of :name, :address, :age end diff --git a/test/db/migrate/002_add_stuff_to_person.rb b/test/db/migrate/002_add_stuff_to_person.rb new file mode 100644 index 0000000..c92dab2 --- /dev/null +++ b/test/db/migrate/002_add_stuff_to_person.rb @@ -0,0 +1,11 @@ +class AddStuffToPerson < ActiveRecord::Migration + def self.up + add_column :people, :bio, :text + add_column :people, :history, :text + end + + def self.down + remove_column :people, :history + remove_column :people, :bio + end +end diff --git a/test/db/schema.rb b/test/db/schema.rb index f14200e..c10a8c5 100644 --- a/test/db/schema.rb +++ b/test/db/schema.rb @@ -9,7 +9,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 1) do +ActiveRecord::Schema.define(:version => 2) do create_table "people", :force => true do |t| t.string "name" @@ -17,6 +17,8 @@ ActiveRecord::Schema.define(:version => 1) do t.integer "age" t.datetime "created_at" t.datetime "updated_at" + t.text "bio" + t.text "history" end end diff --git a/test/test/unit/person_test.rb b/test/test/unit/person_test.rb index 79ed0cb..d576d83 100644 --- a/test/test/unit/person_test.rb +++ b/test/test/unit/person_test.rb @@ -1,8 +1,64 @@ require File.dirname(__FILE__) + '/../test_helper' class PersonTest < ActiveSupport::TestCase - # Replace this with your real tests. - def test_truth - assert true + def setup + super + @person = Person.find :first + @lazy = ['history', 'bio'].to_set + @columns = Person.column_names.to_set - ['id'] + end + + def test_lazy_attributes_values + assert_equal @lazy, @person.class.lazy_attributes + end + + def test_quoted_attributes_should_include_all_columns + assert_equal @columns, @person.send(:attributes_with_quotes).keys.to_set.delete('id') + end + + def test_quoted_attributes_should_exclude_lazy_columns_for_updates + assert_equal @columns - @lazy, attr_for_update(@person) + end + + def test_changed_lazy_attributes_behave_well + @person.name = "Whatever" + assert_equal @columns - @lazy, attr_for_update(@person) + + @person.bio = "Hello world" + assert_equal @columns - ['history'], attr_for_update(@person) + + @person.history = "Nothing." + assert_equal @columns, attr_for_update(@person) + + assert @person.save! + @person.reload + + assert_equal "Nothing.", @person.history + assert_equal "Whatever", @person.name + end + + def test_save_clears_lazy_data + @person.bio = "DSL" + assert_equal @columns - ['history'], attr_for_update(@person) + + assert @person.save + assert_equal @columns - @lazy, attr_for_update(@person) + assert_equal "DSL", @person.bio + end + + def test_reload_clears_lazy_data + @person.bio = "Shenanigans" + @person.history = "Bloody" + assert_equal @columns, attr_for_update(@person) + + assert @person.reload + + assert_equal @columns - @lazy, attr_for_update(@person) + end + + private + + def attr_for_update(person) + person.send(:attributes_with_quotes, false, false).keys.to_set end end -- 2.11.4.GIT