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 99ad91756..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 => class_name.foreign_key, + :foreign_key => self.base_class.name.foreign_key, :dependent => :delete_all, :extend => HasManyExtensions @@ -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 @@ -156,8 +197,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 +240,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..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 @@ -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 @@ -458,6 +458,56 @@ class Baz < ActiveRecord::Base assert Post.last.to_xml =~ /subject/ 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_equal 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_equal [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_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_equal 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| 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