From 923faa70a91551155260139c010796a6e9b9f2dc Mon Sep 17 00:00:00 2001 From: Nick Ewing Date: Thu, 21 Jul 2011 14:41:44 -0700 Subject: [PATCH 1/4] Fix bug with ActiveRecord#attributes causing attributes to be fetched from globalize cache instead of Model's attributes --- lib/globalize/active_record.rb | 11 +++++++---- test/active_record_test.rb | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/globalize/active_record.rb b/lib/globalize/active_record.rb index 99ad91756..b447d67ac 100644 --- a/lib/globalize/active_record.rb +++ b/lib/globalize/active_record.rb @@ -156,8 +156,11 @@ def globalize def attributes self.attribute_names.inject({}) do |attrs, name| - attrs[name] = read_attribute(name) || - (globalize.fetch(I18n.locale, name) rescue nil) + if @attributes.include? name.to_s + attrs[name] = read_attribute(name) + else + attrs[name] = (globalize.fetch(I18n.locale, name) rescue nil) + end attrs end end @@ -196,10 +199,10 @@ def set_translations(options) end end - def reload(options = nil) + def reload(*args) translated_attribute_names.each { |name| @attributes.delete(name.to_s) } globalize.reset - super(options) + super(*args) end protected diff --git a/test/active_record_test.rb b/test/active_record_test.rb index 38e247e17..105562f17 100644 --- a/test/active_record_test.rb +++ b/test/active_record_test.rb @@ -458,6 +458,7 @@ class Baz < ActiveRecord::Base assert Post.last.to_xml =~ /subject/ assert Post.last.to_xml =~ /content/ end + end # TODO error checking for fields that exist in main table, don't exist in From 9b7d13fcab10041f86260903eb645dc3770b4e78 Mon Sep 17 00:00:00 2001 From: Nick Ewing Date: Mon, 29 Aug 2011 17:17:08 -0700 Subject: [PATCH 2/4] Add support for more complex dynamic finds such as find_all_by_x_and_y --- lib/globalize/active_record.rb | 67 +++++++++++++++++++++++++++------- test/active_record_test.rb | 53 ++++++++++++++++++++++++++- test/data/schema.rb | 1 + 3 files changed, 106 insertions(+), 15 deletions(-) diff --git a/lib/globalize/active_record.rb b/lib/globalize/active_record.rb index b447d67ac..4ace97c96 100644 --- a/lib/globalize/active_record.rb +++ b/lib/globalize/active_record.rb @@ -109,28 +109,59 @@ def required_attributes end def respond_to?(method, *args, &block) - method.to_s =~ /^find_by_(\w+)$/ && translated_attribute_names.include?($1.to_sym) || super + !!dynamic_finder(method) || super end def method_missing(method, *args) - if method.to_s =~ /^find_by_(\w+)$/ && translated_attribute_names.include?($1.to_sym) - find_first_by_translated_attr_and_locales($1, args.first) - else - super + match = dynamic_finder(method) + + if match + has_translated_attrs = match.attribute_names.any? do |attribute_name| + translated_attribute_names.include?(attribute_name.to_sym) + end + + return find_by_dynamic_match(match, args) if has_translated_attrs end + + super end protected - def find_first_by_translated_attr_and_locales(name, value) - query = "#{translated_attr_name(name)} = ? AND #{translated_attr_name('locale')} IN (?)" - locales = Globalize.fallbacks(locale || I18n.locale).map(&:to_s) - find( - :first, + def dynamic_finder(method) + match = ::ActiveRecord::DynamicFinderMatch.match(method) + match if match && match.finder? + end + + def find_by_dynamic_match(match, values) + conditions = [] + match.attribute_names.each_with_index do |attribute_name, i| + break if i >= values.size + + if translated_attribute_names.include?(attribute_name.to_sym) + field = translated_attr_name(attribute_name) + else + field = untranslated_attr_name(attribute_name) + end + + conditions << "#{field} = ?" + end + + values.map!(&:to_param) + + conditions << "#{translated_attr_name('locale')} IN (?)" + values << Globalize.fallbacks(locale || I18n.locale).map(&:to_s) + + result = find(match.finder, + :readonly => false, :joins => :translations, - :conditions => [query, value, locales], - :readonly => false - ) + :conditions => values.unshift(conditions.join(" AND "))) + + if match.bang? && !result + raise(::ActiveRecord::RecordNotFound, "Couldn\'t find #{name} with provided values of #{match.attribute_names.join(', ')}") + end + + result end def translated_attr_accessor(name) @@ -138,15 +169,25 @@ def translated_attr_accessor(name) globalize.write(self.class.locale || I18n.locale, name, value) self[name] = value } + define_method name, lambda { |*args| globalize.fetch(args.first || self.class.locale || I18n.locale, name) } + + define_method "#{name}?", lambda { |*args| + globalize.fetch(args.first || self.class.locale || I18n.locale, name).present? + } + alias_method "#{name}_before_type_cast", name end def translated_attr_name(name) "#{translation_class.table_name}.#{name}" end + + def untranslated_attr_name(name) + "#{table_name}.#{name}" + end end module InstanceMethods diff --git a/test/active_record_test.rb b/test/active_record_test.rb index 105562f17..57d52cccc 100644 --- a/test/active_record_test.rb +++ b/test/active_record_test.rb @@ -445,12 +445,12 @@ class Baz < ActiveRecord::Base test "attribute_names returns translated and regular attribute names" do Post.create :subject => "foo", :content => "bar" - assert_equal Post.last.attribute_names.sort, %w[blog_id content id subject] + assert_equal Post.last.attribute_names.sort, %w[blog_id content id label subject] end test "attributes returns translated and regular attributes" do Post.create :subject => "foo", :content => "bar" - assert_equal Post.last.attributes.keys.sort, %w[blog_id content id subject] + assert_equal Post.last.attributes.keys.sort, %w[blog_id content id label subject] end test "to_xml includes translated fields" do @@ -459,6 +459,55 @@ class Baz < ActiveRecord::Base assert Post.last.to_xml =~ /content/ end + test "should create presence methods for attributes" do + post1 = Post.create :subject => "foo", :content => nil + post2 = Post.create :subject => "" + + assert post1.respond_to?(:subject?) + assert post1.respond_to?(:content?) + + assert post1.subject? + assert !post1.content? + assert !post2.subject? + end + + test "dynamic find first matcher on multiple attributes" do + created = Post.create :subject => "foo", :content => "bar", :label => "dummy" + found = Post.find_by_subject_and_label("foo", "dummy") + + assert created, found + end + + test "dynamic find all matcher on multiple attributes" do + created1 = Post.create :subject => "foo", :content => "bar", :label => "dummy" + created2 = Post.create :subject => "foo", :content => "baz", :label => "dummy" + + found = Post.find_all_by_subject_and_label("foo", "dummy") + + assert [created1, created2], found + end + + test "dynamic find last matcher on multiple attributes" do + created1 = Post.create :subject => "foo", :content => "bar", :label => "dummy" + created2 = Post.create :subject => "foo", :content => "baz", :label => "dummy" + + found = Post.find_last_by_subject_and_label("foo", "dummy") + + assert created2, found + end + + test "dynamic find first matcher with too few values" do + created = Post.create :subject => "foo", :content => "bar", :label => "dummy" + found = Post.find_by_subject_and_label("foo") + + assert created, found + end + + test "dynamic find first matcher with bang" do + assert_raise(::ActiveRecord::RecordNotFound) do + Post.find_by_subject_and_label!("does_not_exist") + end + end end # TODO error checking for fields that exist in main table, don't exist in diff --git a/test/data/schema.rb b/test/data/schema.rb index 910dd0855..96393f0d2 100644 --- a/test/data/schema.rb +++ b/test/data/schema.rb @@ -5,6 +5,7 @@ create_table :posts, :force => true do |t| t.references :blog + t.string :label end create_table :post_translations, :force => true do |t| From 4b875037cfaac57863d9a28ce8905756df387f43 Mon Sep 17 00:00:00 2001 From: Scott Willson Date: Mon, 28 Nov 2011 13:25:19 -0800 Subject: [PATCH 3/4] Replace deprecated class_name call --- lib/globalize/active_record.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/globalize/active_record.rb b/lib/globalize/active_record.rb index 4ace97c96..48bdf9e99 100644 --- a/lib/globalize/active_record.rb +++ b/lib/globalize/active_record.rb @@ -54,7 +54,7 @@ def translates(*attr_names) after_save :save_translations! has_many :translations, :class_name => translation_class.name, - :foreign_key => class_name.foreign_key, + :foreign_key => self.name.foreign_key, :dependent => :delete_all, :extend => HasManyExtensions From 7410b8e7e1e639d00bf9e01be45f7f38afb9a6a4 Mon Sep 17 00:00:00 2001 From: Nick Ewing Date: Mon, 4 Jun 2012 14:25:01 -0700 Subject: [PATCH 4/4] Update to work with Ruby 1.9 and Rails 2.3.14 --- Rakefile | 6 +++--- lib/globalize/active_record.rb | 4 ++-- test/active_record_test.rb | 20 ++++++++++---------- test/test_helper.rb | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Rakefile b/Rakefile index bc35dada4..459816f02 100644 --- a/Rakefile +++ b/Rakefile @@ -1,14 +1,14 @@ require 'rake' require 'rake/testtask' -require 'rake/rdoctask' +require 'rdoc/task' desc 'Default: run unit tests.' task :default => :test desc 'Test the globalize2 plugin.' Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.pattern = 'test/**/*_test.rb' + t.libs << "test" + t.test_files = FileList['test/**/*_test.rb'] t.verbose = true end diff --git a/lib/globalize/active_record.rb b/lib/globalize/active_record.rb index 48bdf9e99..d8c7a1ce9 100644 --- a/lib/globalize/active_record.rb +++ b/lib/globalize/active_record.rb @@ -22,7 +22,7 @@ def build_translation_class(target, options) klass.class_eval do set_table_name(options[:table_name]) - belongs_to target.name.underscore.gsub('/', '_') + belongs_to target.base_class.name.underscore.gsub('/', '_') def locale; read_attribute(:locale).to_sym; end def locale=(locale); write_attribute(:locale, locale.to_s); end end @@ -54,7 +54,7 @@ def translates(*attr_names) after_save :save_translations! has_many :translations, :class_name => translation_class.name, - :foreign_key => self.name.foreign_key, + :foreign_key => self.base_class.name.foreign_key, :dependent => :delete_all, :extend => HasManyExtensions diff --git a/test/active_record_test.rb b/test/active_record_test.rb index 57d52cccc..4c7e724ae 100644 --- a/test/active_record_test.rb +++ b/test/active_record_test.rb @@ -321,9 +321,9 @@ def translations_included post.set_translations options post.reload - assert ["bar2", "bar2"], [post.subject, post.content] + assert_equal ["bar2", "bar2"], [post.subject, post.content] Post.locale = :de - assert ["foo2", "foo2"], [post.subject, post.content] + assert_equal ["foo2", "foo2"], [post.subject, post.content] end test "setting only one translation with set_translations" do @@ -336,9 +336,9 @@ def translations_included post.set_translations options post.reload - assert ["bar2", "bar2"], [post.subject, post.content] + assert_equal ["bar2", "bar2"], [post.subject, post.content] Post.locale = :de - assert ["foo1", "foo1"], [post.subject, post.content] + assert_equal ["foo1", "foo1"], [post.subject, post.content] end test "setting only selected attributes with set_translations" do @@ -351,9 +351,9 @@ def translations_included post.set_translations options post.reload - assert ["bar2", "bar1"], [post.subject, post.content] + assert_equal ["bar2", "bar1"], [post.subject, post.content] Post.locale = :de - assert ["foo1", "foo2"], [post.subject, post.content] + assert_equal ["foo1", "foo2"], [post.subject, post.content] end test "setting invalid attributes raises ArgumentError" do @@ -475,7 +475,7 @@ class Baz < ActiveRecord::Base created = Post.create :subject => "foo", :content => "bar", :label => "dummy" found = Post.find_by_subject_and_label("foo", "dummy") - assert created, found + assert_equal created, found end test "dynamic find all matcher on multiple attributes" do @@ -484,7 +484,7 @@ class Baz < ActiveRecord::Base found = Post.find_all_by_subject_and_label("foo", "dummy") - assert [created1, created2], found + assert_equal [created1, created2], found end test "dynamic find last matcher on multiple attributes" do @@ -493,14 +493,14 @@ class Baz < ActiveRecord::Base found = Post.find_last_by_subject_and_label("foo", "dummy") - assert created2, found + assert_equal created2, found end test "dynamic find first matcher with too few values" do created = Post.create :subject => "foo", :content => "bar", :label => "dummy" found = Post.find_by_subject_and_label("foo") - assert created, found + assert_equal created, found end test "dynamic find first matcher with bang" do diff --git a/test/test_helper.rb b/test/test_helper.rb index 99a5d3950..bb40d8d79 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -41,7 +41,7 @@ def assert_has_many(model, associated) module ActiveRecord module ConnectionAdapters class AbstractAdapter - def index_exists?(table_name, column_name) + def index_exists?(table_name, column_name, options = {}) indexes(table_name).any? { |index| index.name == index_name(table_name, column_name) } end end @@ -73,4 +73,4 @@ def index_exists?(table_name, column_name) # end # end # end -# end \ No newline at end of file +# end