Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug with Globalize::ActiveRecord#attributes #54

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -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

Expand Down
82 changes: 63 additions & 19 deletions lib/globalize/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -109,44 +109,85 @@ 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)
define_method "#{name}=", lambda { |value|
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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
66 changes: 58 additions & 8 deletions test/active_record_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -445,19 +445,69 @@ 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
Post.create :subject => "foo", :content => "bar"
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
Expand Down
1 change: 1 addition & 0 deletions test/data/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
4 changes: 2 additions & 2 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -73,4 +73,4 @@ def index_exists?(table_name, column_name)
# end
# end
# end
# end
# end