has_localization_table 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/has_localization_table/active_record/attributes.rb +47 -0
- data/lib/has_localization_table/active_record/callbacks.rb +24 -0
- data/lib/has_localization_table/active_record/finder_methods.rb +33 -0
- data/lib/has_localization_table/active_record/meta_methods.rb +55 -0
- data/lib/has_localization_table/active_record/ordered_by.rb +28 -0
- data/lib/has_localization_table/active_record/relation.rb +46 -0
- data/lib/has_localization_table/active_record/validations.rb +26 -0
- data/lib/has_localization_table/active_record.rb +14 -2
- data/lib/has_localization_table/version.rb +1 -1
- data/lib/has_localization_table.rb +0 -2
- data/spec/active_record/attributes_spec.rb +132 -0
- data/spec/active_record/finder_method_spec.rb +29 -0
- data/spec/active_record/ordered_by_spec.rb +34 -0
- data/spec/active_record/relation_spec.rb +20 -0
- data/spec/active_record/validation_spec.rb +62 -0
- data/spec/active_record_spec.rb +0 -147
- data/spec/spec_helper.rb +1 -0
- data/spec/support/setup.rb +28 -0
- metadata +28 -11
- data/lib/has_localization_table/class_methods.rb +0 -127
- data/lib/has_localization_table/instance_methods.rb +0 -78
@@ -0,0 +1,47 @@
|
|
1
|
+
module HasLocalizationTable
|
2
|
+
module ActiveRecord
|
3
|
+
module Attributes
|
4
|
+
def read_localized_attribute(attribute, locale = HasLocalizationTable.current_locale)
|
5
|
+
attribute_cache[attribute.to_sym][locale.id] ||= localization_association.detect{ |a| a.send(HasLocalizationTable.locale_foreign_key) == locale.id }.send(attribute) rescue nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def write_localized_attribute(attribute, value, locale = HasLocalizationTable.current_locale)
|
9
|
+
value = value.to_s
|
10
|
+
localization = localization_association.detect{ |a| a.send(HasLocalizationTable.locale_foreign_key) == locale.id } ||
|
11
|
+
localization_association.build(HasLocalizationTable.locale_foreign_key => locale.id)
|
12
|
+
|
13
|
+
localization.send(:"#{attribute}=", value)
|
14
|
+
attribute_cache[attribute.to_sym][locale.id] = value.blank? ? nil : value
|
15
|
+
end
|
16
|
+
|
17
|
+
# Define attribute getters and setters
|
18
|
+
def method_missing(name, *args, &block)
|
19
|
+
if name.to_s =~ /\A([a-z0-9_]+)(=)?\Z/i
|
20
|
+
if localized_attributes.include?($1.to_sym)
|
21
|
+
if $2.nil? # No equals sign -- not a setter
|
22
|
+
# Try to load a string for the given locale
|
23
|
+
# If that fails, try for the primary locale
|
24
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 0 or 1)" unless args.size.between?(0, 1)
|
25
|
+
return read_localized_attribute($1, *args) || read_localized_attribute($1, HasLocalizationTable.primary_locale)
|
26
|
+
else
|
27
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" unless args.size == 1
|
28
|
+
return write_localized_attribute($1, args.first)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def respond_to?(*args)
|
37
|
+
return true if args.first.to_s =~ /\A([a-z0-9_]+)=?\Z/i and localized_attributes.include?($1.to_sym)
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def attribute_cache
|
43
|
+
@localization_attribute_cache ||= localized_attributes.inject({}) { |memo, attr| memo[attr] = {}; memo }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module HasLocalizationTable
|
2
|
+
module ActiveRecord
|
3
|
+
module Callbacks
|
4
|
+
def setup_localization_callbacks!
|
5
|
+
# Initialize string records after main record initialization
|
6
|
+
after_initialize do
|
7
|
+
build_missing_localizations!
|
8
|
+
end
|
9
|
+
|
10
|
+
before_validation do
|
11
|
+
reject_empty_localizations!
|
12
|
+
build_missing_localizations!
|
13
|
+
end
|
14
|
+
|
15
|
+
# Reject any blank strings before saving the record
|
16
|
+
# Validation will have happened by this point, so if there is a required string that is needed, it won't be rejected
|
17
|
+
before_save do
|
18
|
+
reject_empty_localizations!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
private :setup_localization_callbacks!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module HasLocalizationTable
|
2
|
+
module ActiveRecord
|
3
|
+
module FinderMethods
|
4
|
+
def method_missing(name, *args, &block)
|
5
|
+
if name.to_s =~ /\Afind_by_([a-z0-9_]+(_and_[a-z0-9_]+)*)\Z/
|
6
|
+
attributes = $1.split("_and_").map(&:to_sym)
|
7
|
+
if (attributes & localized_attributes).size == attributes.size
|
8
|
+
raise ArgumentError, "expected #{attributes.size} #{"argument".pluralize(attributes.size)}: #{attributes.join(", ")}" unless args.size == attributes.size
|
9
|
+
args = attributes.zip(args).inject({}) { |memo, (key, val)| memo[key] = val; memo }
|
10
|
+
return find_by_localized_attributes(args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond_to?(*args)
|
18
|
+
if args.first.to_s =~ /\Afind_by_([a-z0-9_]+(_and_[a-z0-9_]+)*)\Z/
|
19
|
+
attributes = $1.split("_and_").map(&:to_sym)
|
20
|
+
return true if (attributes & localized_attributes).size == attributes.size
|
21
|
+
end
|
22
|
+
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
# Find a record by multiple localization values
|
28
|
+
def find_by_localized_attributes(attributes, locale = HasLocalizationTable.current_locale)
|
29
|
+
with_localizations.where(localization_class.table_name => attributes).first
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module HasLocalizationTable
|
2
|
+
module ActiveRecord
|
3
|
+
module MetaMethods
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.send(:include, InstanceMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
def localization_class
|
9
|
+
localization_table_options[:class_name].constantize
|
10
|
+
end
|
11
|
+
|
12
|
+
def localization_association_name
|
13
|
+
localization_table_options[:association_name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def localized_attributes
|
17
|
+
# Determine which attributes of the association model should be accessable through the base class
|
18
|
+
# ie. everything that's not a primary key, foreign key, or timestamp attribute
|
19
|
+
association_name = self.localization_table_options[:association_name] || :strings
|
20
|
+
association = reflect_on_association(association_name)
|
21
|
+
|
22
|
+
attribute_names = association.klass.attribute_names
|
23
|
+
timestamp_attrs = association.klass.new.send(:all_timestamp_attributes_in_model).map(&:to_s)
|
24
|
+
foreign_keys = association.klass.reflect_on_all_associations.map{ |a| a.association_foreign_key }
|
25
|
+
primary_keys = [association.klass.primary_key]
|
26
|
+
# protected_attrs = association.klass.protected_attributes.to_a
|
27
|
+
|
28
|
+
(attribute_names - timestamp_attrs - foreign_keys - primary_keys).map(&:to_sym)
|
29
|
+
end
|
30
|
+
|
31
|
+
def localized_attribute_required?(attribute)
|
32
|
+
return false unless localization_table_options[:required] || false
|
33
|
+
return true unless localization_table_options[:optional]
|
34
|
+
|
35
|
+
!localization_table_options[:optional].include?(attribute)
|
36
|
+
end
|
37
|
+
|
38
|
+
module InstanceMethods
|
39
|
+
# Helper method for getting the localization association without having to look up the name each time
|
40
|
+
def localization_association
|
41
|
+
association_name = localization_table_options[:association_name]
|
42
|
+
send(association_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def localized_attributes
|
46
|
+
self.class.localized_attributes
|
47
|
+
end
|
48
|
+
|
49
|
+
def localization_table_options
|
50
|
+
self.class.localization_table_options
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module HasLocalizationTable
|
2
|
+
module ActiveRecord
|
3
|
+
module OrderedBy
|
4
|
+
def method_missing(name, *args, &block)
|
5
|
+
if name.to_s =~ /\Aordered_by_([a-z0-9_]+)\Z/
|
6
|
+
attribute = $1.to_sym
|
7
|
+
return ordered_by_localized_attribute(attribute, *args) if localized_attributes.include?(attribute)
|
8
|
+
end
|
9
|
+
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to?(*args)
|
14
|
+
if args.first.to_s =~ /\Aordered_by_([a-z0-9_]+)\Z/
|
15
|
+
return true if localized_attributes.include?($1.to_sym)
|
16
|
+
end
|
17
|
+
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
# Order records by localization value
|
23
|
+
def ordered_by_localized_attribute(attribute, asc = true, locale = HasLocalizationTable.current_locale)
|
24
|
+
with_localizations.order("#{localization_class.table_name}.#{attribute} #{asc ? "ASC" : "DESC"}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module HasLocalizationTable
|
2
|
+
module ActiveRecord
|
3
|
+
module Relation
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.send(:include, InstanceMethods)
|
6
|
+
|
7
|
+
# Alias the scoping method to use the actual association name
|
8
|
+
alias_method :"with_#{klass.localization_association_name}", :with_localizations
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_localizations
|
12
|
+
lcat = localization_class.arel_table
|
13
|
+
|
14
|
+
scoped.joins(
|
15
|
+
arel_table.join(lcat, Arel::Nodes::OuterJoin).
|
16
|
+
on(lcat[:"#{self.name.underscore}_id"].eq(arel_table[self.primary_key]).and(lcat[HasLocalizationTable.locale_foreign_key].eq(HasLocalizationTable.current_locale.id))).
|
17
|
+
join_sql
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_localization_association!
|
22
|
+
self.has_many localization_association_name, localization_table_options.except(:association_name, :required, :optional)
|
23
|
+
end
|
24
|
+
private :create_localization_association!
|
25
|
+
|
26
|
+
module InstanceMethods
|
27
|
+
# Add localization objects for any available locale that doesn't have one
|
28
|
+
def build_missing_localizations!
|
29
|
+
locale_ids = HasLocalizationTable.all_locales.map(&:id)
|
30
|
+
HasLocalizationTable.all_locales.each do |locale|
|
31
|
+
unless localization_association.detect{ |str| str.send(HasLocalizationTable.locale_foreign_key) == locale.id }
|
32
|
+
localization_association.build(HasLocalizationTable.locale_foreign_key => locale.id)
|
33
|
+
end
|
34
|
+
|
35
|
+
localization_association.sort_by!{ |l| locale_ids.index(l.send(HasLocalizationTable.locale_foreign_key)) || 0 }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Remove localization objects that are not filled in
|
40
|
+
def reject_empty_localizations!
|
41
|
+
localization_association.reject! { |l| !l.persisted? and localized_attributes.all?{ |attr| l.send(attr).blank? } }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module HasLocalizationTable
|
2
|
+
module ActiveRecord
|
3
|
+
module Validations
|
4
|
+
def setup_localization_validations!
|
5
|
+
localized_attributes.each do |attribute|
|
6
|
+
# Add validation to make all string fields required for the primary locale
|
7
|
+
obj = self
|
8
|
+
localization_class.class_eval do
|
9
|
+
validates attribute, presence: { message: :custom_this_field_is_required },
|
10
|
+
if: proc { |model| obj.name.constantize.localized_attribute_required?(attribute) && model.send(HasLocalizationTable.locale_foreign_key) == HasLocalizationTable.current_locale.id }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add validation to ensure a string for the primary locale exists if the string is required
|
15
|
+
validate do
|
16
|
+
if localization_table_options[:required] || false
|
17
|
+
errors.add(localization_association_name, :primary_lang_string_required) unless localization_association.any? do |string|
|
18
|
+
string.send(HasLocalizationTable.locale_foreign_key) == HasLocalizationTable.primary_locale.id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
private :setup_localization_validations!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,5 +1,13 @@
|
|
1
1
|
module HasLocalizationTable
|
2
2
|
module ActiveRecord
|
3
|
+
autoload :Attributes, 'has_localization_table/active_record/attributes'
|
4
|
+
autoload :Callbacks, 'has_localization_table/active_record/callbacks'
|
5
|
+
autoload :FinderMethods, 'has_localization_table/active_record/finder_methods'
|
6
|
+
autoload :MetaMethods, 'has_localization_table/active_record/meta_methods'
|
7
|
+
autoload :OrderedBy, 'has_localization_table/active_record/ordered_by'
|
8
|
+
autoload :Relation, 'has_localization_table/active_record/relation'
|
9
|
+
autoload :Validations, 'has_localization_table/active_record/validations'
|
10
|
+
|
3
11
|
def has_localization_table(*args)
|
4
12
|
options = args.extract_options!
|
5
13
|
options[:association_name] = args.first || HasLocalizationTable.default_association_name
|
@@ -8,8 +16,12 @@ module HasLocalizationTable
|
|
8
16
|
class_attribute :localization_table_options
|
9
17
|
self.localization_table_options = { dependent: :delete_all, class_name: self.name + HasLocalizationTable.class_suffix }.merge(options)
|
10
18
|
|
11
|
-
extend
|
12
|
-
include
|
19
|
+
extend Relation, FinderMethods, OrderedBy, Callbacks, Validations, MetaMethods
|
20
|
+
include Attributes
|
21
|
+
|
22
|
+
create_localization_association!
|
23
|
+
setup_localization_callbacks!
|
24
|
+
setup_localization_validations!
|
13
25
|
end
|
14
26
|
end
|
15
27
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
require "has_localization_table/version"
|
2
2
|
require "has_localization_table/config"
|
3
|
-
require "has_localization_table/class_methods"
|
4
|
-
require "has_localization_table/instance_methods"
|
5
3
|
require "has_localization_table/active_record"
|
6
4
|
|
7
5
|
ActiveRecord::Base.extend(HasLocalizationTable::ActiveRecord) if defined?(ActiveRecord::Base)
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HasLocalizationTable do
|
4
|
+
before do
|
5
|
+
# Configure HLT
|
6
|
+
HasLocalizationTable.configure do |c|
|
7
|
+
c.primary_locale = Locale.first
|
8
|
+
c.current_locale = Locale.first
|
9
|
+
c.all_locales = Locale.all
|
10
|
+
end
|
11
|
+
|
12
|
+
Object.send(:remove_const, :Article) rescue nil
|
13
|
+
Article = Class.new(ActiveRecord::Base)
|
14
|
+
Article.has_localization_table
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:a) { Article.new(name: "Test", description: "Description") }
|
18
|
+
|
19
|
+
it "should set localized attributes" do
|
20
|
+
a.localizations.first.name.must_equal "Test"
|
21
|
+
a.localizations.first.description.must_equal "Description"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should create accessor methods" do
|
25
|
+
a.name.must_equal "Test"
|
26
|
+
a.description.must_equal "Description"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should save localized attributes" do
|
30
|
+
a.save!
|
31
|
+
a.reload
|
32
|
+
a.name.must_equal "Test"
|
33
|
+
a.description.must_equal "Description"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should create mutator methods" do
|
37
|
+
a.name = "Changed"
|
38
|
+
a.description = "Changed Description"
|
39
|
+
a.name.must_equal "Changed"
|
40
|
+
a.description.must_equal "Changed Description"
|
41
|
+
a.localizations.first.name.must_equal "Changed"
|
42
|
+
a.localizations.first.description.must_equal "Changed Description"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should use the current locale when setting" do
|
46
|
+
a
|
47
|
+
|
48
|
+
HasLocalizationTable.configure do |c|
|
49
|
+
c.current_locale = Locale.last
|
50
|
+
end
|
51
|
+
|
52
|
+
a.name = "French Name"
|
53
|
+
a.description = "French Description"
|
54
|
+
|
55
|
+
eng = a.localizations.detect{ |s| s.locale_id == Locale.first.id }
|
56
|
+
fre = a.localizations.detect{ |s| s.locale_id == Locale.last.id }
|
57
|
+
|
58
|
+
eng.name.must_equal "Test"
|
59
|
+
eng.description.must_equal "Description"
|
60
|
+
fre.name.must_equal "French Name"
|
61
|
+
fre.description.must_equal "French Description"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return the correct value when the current locale changes" do
|
65
|
+
Locale.class_eval { cattr_accessor :current }
|
66
|
+
eng = Locale.find_by_name("English")
|
67
|
+
fre = Locale.find_by_name("French")
|
68
|
+
|
69
|
+
HasLocalizationTable.configure do |c|
|
70
|
+
c.current_locale = ->{ Locale.current }
|
71
|
+
end
|
72
|
+
|
73
|
+
Locale.current = eng
|
74
|
+
a.name = "English Name"
|
75
|
+
a.description = "English Description"
|
76
|
+
|
77
|
+
Locale.current = fre
|
78
|
+
a.name = "French Name"
|
79
|
+
a.description = "French Description"
|
80
|
+
|
81
|
+
Locale.current = eng
|
82
|
+
a.name.must_equal "English Name"
|
83
|
+
a.description.must_equal "English Description"
|
84
|
+
|
85
|
+
Locale.current = fre
|
86
|
+
a.name.must_equal "French Name"
|
87
|
+
a.description.must_equal "French Description"
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should return the correct locale's value even if the cache is empty" do
|
91
|
+
Locale.class_eval { cattr_accessor :current }
|
92
|
+
Locale.current = eng = Locale.find_by_name("English")
|
93
|
+
fre = Locale.find_by_name("French")
|
94
|
+
|
95
|
+
HasLocalizationTable.configure do |c|
|
96
|
+
c.current_locale = ->{ Locale.current }
|
97
|
+
end
|
98
|
+
|
99
|
+
a.localizations.last.attributes = { name: "French Name", description: "French Description" }
|
100
|
+
|
101
|
+
# Force empty cache
|
102
|
+
a.instance_variable_set(:@localization_attribute_cache, { name: {}, description: {} })
|
103
|
+
|
104
|
+
Locale.current = fre
|
105
|
+
a.name.must_equal "French Name"
|
106
|
+
a.description.must_equal "French Description"
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should return the correct locale's value even if a language was added" do
|
110
|
+
Locale.class_eval { cattr_accessor :current }
|
111
|
+
Locale.current = eng = Locale.find_by_name("English")
|
112
|
+
fre = Locale.find_by_name("French")
|
113
|
+
|
114
|
+
HasLocalizationTable.configure do |c|
|
115
|
+
c.current_locale = ->{ Locale.current }
|
116
|
+
c.all_locales = [eng]
|
117
|
+
end
|
118
|
+
|
119
|
+
Object.send(:remove_const, :Article) rescue nil
|
120
|
+
Article = Class.new(ActiveRecord::Base)
|
121
|
+
Article.has_localization_table
|
122
|
+
|
123
|
+
aa = Article.create!(name: "Name", description: "Description")
|
124
|
+
l = ArticleLocalization.create!(article: aa, locale: fre, name: "French Name", description: "French Description")
|
125
|
+
|
126
|
+
aa.reload
|
127
|
+
|
128
|
+
Locale.current = fre
|
129
|
+
aa.name.must_equal "French Name"
|
130
|
+
aa.description.must_equal "French Description"
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HasLocalizationTable do
|
4
|
+
before do
|
5
|
+
# Configure HLT
|
6
|
+
HasLocalizationTable.configure do |c|
|
7
|
+
c.primary_locale = Locale.first
|
8
|
+
c.current_locale = Locale.first
|
9
|
+
c.all_locales = Locale.all
|
10
|
+
end
|
11
|
+
|
12
|
+
Object.send(:remove_const, :Article) rescue nil
|
13
|
+
Article = Class.new(ActiveRecord::Base)
|
14
|
+
Article.has_localization_table
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:a) { Article.new(name: "Test", description: "Description") }
|
18
|
+
|
19
|
+
it "should create finder methods" do
|
20
|
+
a.save!
|
21
|
+
Article.find_by_name("Test").must_equal a
|
22
|
+
Article.find_by_description("Description").must_equal a
|
23
|
+
Article.find_by_name_and_description("Test", "Description").must_equal a
|
24
|
+
Article.find_by_description_and_name("Description", "Test").must_equal a
|
25
|
+
|
26
|
+
Article.find_by_name("Wrong").must_be_nil
|
27
|
+
Article.find_by_description("Wrong").must_be_nil
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HasLocalizationTable do
|
4
|
+
before do
|
5
|
+
# Configure HLT
|
6
|
+
HasLocalizationTable.configure do |c|
|
7
|
+
c.primary_locale = Locale.first
|
8
|
+
c.current_locale = Locale.first
|
9
|
+
c.all_locales = Locale.all
|
10
|
+
end
|
11
|
+
|
12
|
+
Object.send(:remove_const, :Article) rescue nil
|
13
|
+
Article = Class.new(ActiveRecord::Base)
|
14
|
+
Article.has_localization_table
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:a) { Article.new(name: "Test", description: "Description") }
|
18
|
+
|
19
|
+
it "should create ordered_by methods" do
|
20
|
+
a.save!
|
21
|
+
b = Article.create!(name: "Name", description: "Another Description")
|
22
|
+
c = Article.create!(name: "Once Upon a Time...", description: "Fairytale")
|
23
|
+
Article.ordered_by_name.must_equal [b, c, a]
|
24
|
+
Article.ordered_by_description.must_equal [b, a, c]
|
25
|
+
Article.ordered_by_name(false).must_equal [a, c, b]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should allow ordered_by methods to apply to scope chains" do
|
29
|
+
a.save!
|
30
|
+
b = Article.create!(name: "Name", description: "Another Description")
|
31
|
+
c = Article.create!(name: "Once Upon a Time...", description: "Fairytale")
|
32
|
+
Article.scoped.ordered_by_name.must_equal [b, c, a]
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HasLocalizationTable do
|
4
|
+
before do
|
5
|
+
# Configure HLT
|
6
|
+
HasLocalizationTable.configure do |c|
|
7
|
+
c.primary_locale = Locale.first
|
8
|
+
c.current_locale = Locale.first
|
9
|
+
c.all_locales = Locale.all
|
10
|
+
end
|
11
|
+
|
12
|
+
Object.send(:remove_const, :Article) rescue nil
|
13
|
+
Article = Class.new(ActiveRecord::Base)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should alias with_localizations with the actual association name" do
|
17
|
+
Article.has_localization_table :strings
|
18
|
+
assert Article.respond_to? :with_strings
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HasLocalizationTable do
|
4
|
+
before do
|
5
|
+
# Configure HLT
|
6
|
+
HasLocalizationTable.configure do |c|
|
7
|
+
c.primary_locale = Locale.first
|
8
|
+
c.current_locale = Locale.first
|
9
|
+
c.all_locales = Locale.all
|
10
|
+
end
|
11
|
+
|
12
|
+
Object.send(:remove_const, :Article) rescue nil
|
13
|
+
Article = Class.new(ActiveRecord::Base)
|
14
|
+
Article.has_localization_table
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:a) { Article.new(name: "Test", description: "Description") }
|
18
|
+
|
19
|
+
it "should add validations if given required: true" do
|
20
|
+
Article.has_localization_table required: true
|
21
|
+
a = Article.new
|
22
|
+
refute a.valid?
|
23
|
+
a.errors[:localizations].wont_be_empty
|
24
|
+
|
25
|
+
a = Article.new(description: "Wishing the world hello!")
|
26
|
+
s = a.localizations.first
|
27
|
+
refute s.valid?
|
28
|
+
s.errors[:name].wont_be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not add validations if given required: false" do
|
32
|
+
Article.has_localization_table required: false
|
33
|
+
a = Article.new
|
34
|
+
assert a.valid?
|
35
|
+
a.errors[:localizations].must_be_empty
|
36
|
+
|
37
|
+
a = Article.new(description: "Wishing the world hello!")
|
38
|
+
s = a.localizations.first
|
39
|
+
assert s.valid?
|
40
|
+
s.errors[:name].must_be_empty
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should not add validations if required is not given" do
|
44
|
+
Article.has_localization_table
|
45
|
+
a = Article.new
|
46
|
+
assert a.valid?
|
47
|
+
a.errors[:localizations].must_be_empty
|
48
|
+
|
49
|
+
a = Article.new(description: "Wishing the world hello!")
|
50
|
+
s = a.localizations.first
|
51
|
+
assert s.valid?
|
52
|
+
s.errors[:name].must_be_empty
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not add validations for optional fields" do
|
56
|
+
Article.has_localization_table required: true, optional: [:description]
|
57
|
+
a = Article.new(name: "Test")
|
58
|
+
assert a.valid?
|
59
|
+
a.errors[:localizations].must_be_empty
|
60
|
+
assert a.localizations.all?{ |s| s.errors[:description].empty? }
|
61
|
+
end
|
62
|
+
end
|
data/spec/active_record_spec.rb
CHANGED
@@ -1,34 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
# Setup in-memory database so AR can work
|
4
|
-
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
5
|
-
ActiveRecord::Migration.tap do |m|
|
6
|
-
m.create_table :articles do |t|
|
7
|
-
t.timestamps
|
8
|
-
end
|
9
|
-
|
10
|
-
m.create_table :locales do |t|
|
11
|
-
t.string :name
|
12
|
-
end
|
13
|
-
|
14
|
-
m.create_table :article_localizations do |t|
|
15
|
-
t.integer :article_id
|
16
|
-
t.integer :locale_id
|
17
|
-
t.string :name
|
18
|
-
t.string :description
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# Set up locales
|
23
|
-
Locale = Class.new(ActiveRecord::Base)
|
24
|
-
Locale.create!(name: "English")
|
25
|
-
Locale.create!(name: "French")
|
26
|
-
|
27
|
-
ArticleLocalization = Class.new(ActiveRecord::Base) do
|
28
|
-
belongs_to :article
|
29
|
-
belongs_to :locale
|
30
|
-
end
|
31
|
-
|
32
3
|
describe HasLocalizationTable do
|
33
4
|
before do
|
34
5
|
# Configure HLT
|
@@ -76,123 +47,5 @@ describe HasLocalizationTable do
|
|
76
47
|
|
77
48
|
Object.send(:remove_const, :ArticleText)
|
78
49
|
end
|
79
|
-
|
80
|
-
it "should add validations if given required: true" do
|
81
|
-
Article.has_localization_table required: true
|
82
|
-
a = Article.new
|
83
|
-
refute a.valid?
|
84
|
-
a.errors[:localizations].wont_be_empty
|
85
|
-
|
86
|
-
a = Article.new(description: "Wishing the world hello!")
|
87
|
-
s = a.localizations.first
|
88
|
-
refute s.valid?
|
89
|
-
s.errors[:name].wont_be_empty
|
90
|
-
end
|
91
|
-
|
92
|
-
it "should not add validations if given required: false" do
|
93
|
-
Article.has_localization_table required: false
|
94
|
-
a = Article.new
|
95
|
-
a.valid? or raise a.localizations.map(&:errors).inspect
|
96
|
-
a.errors[:localizations].must_be_empty
|
97
|
-
|
98
|
-
a = Article.new(description: "Wishing the world hello!")
|
99
|
-
s = a.localizations.first
|
100
|
-
assert s.valid?
|
101
|
-
s.errors[:name].must_be_empty
|
102
|
-
end
|
103
|
-
|
104
|
-
it "should not add validations if required is not given" do
|
105
|
-
Article.has_localization_table
|
106
|
-
a = Article.new
|
107
|
-
assert a.valid?
|
108
|
-
a.errors[:localizations].must_be_empty
|
109
|
-
|
110
|
-
a = Article.new(description: "Wishing the world hello!")
|
111
|
-
s = a.localizations.first
|
112
|
-
assert s.valid?
|
113
|
-
s.errors[:name].must_be_empty
|
114
|
-
end
|
115
|
-
|
116
|
-
it "should not add validations for optional fields" do
|
117
|
-
Article.has_localization_table required: true, optional: [:description]
|
118
|
-
a = Article.new(name: "Test")
|
119
|
-
assert a.valid?
|
120
|
-
a.errors[:localizations].must_be_empty
|
121
|
-
assert a.localizations.all?{ |s| s.errors[:description].empty? }
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
describe "other methods" do
|
126
|
-
before do
|
127
|
-
Object.send(:remove_const, :Article) rescue nil
|
128
|
-
Article = Class.new(ActiveRecord::Base)
|
129
|
-
Article.has_localization_table
|
130
|
-
end
|
131
|
-
|
132
|
-
let(:a) { Article.new(name: "Test", description: "Description") }
|
133
|
-
|
134
|
-
it "should set localized attributes" do
|
135
|
-
a.localizations.first.name.must_equal "Test"
|
136
|
-
a.localizations.first.description.must_equal "Description"
|
137
|
-
end
|
138
|
-
|
139
|
-
it "should create accessor methods" do
|
140
|
-
a.name.must_equal "Test"
|
141
|
-
a.description.must_equal "Description"
|
142
|
-
end
|
143
|
-
|
144
|
-
it "should save localized attributes" do
|
145
|
-
a.save!
|
146
|
-
a.reload
|
147
|
-
a.name.must_equal "Test"
|
148
|
-
a.description.must_equal "Description"
|
149
|
-
end
|
150
|
-
|
151
|
-
it "should create mutator methods" do
|
152
|
-
a.name = "Changed"
|
153
|
-
a.description = "Changed Description"
|
154
|
-
a.name.must_equal "Changed"
|
155
|
-
a.description.must_equal "Changed Description"
|
156
|
-
a.localizations.first.name.must_equal "Changed"
|
157
|
-
a.localizations.first.description.must_equal "Changed Description"
|
158
|
-
end
|
159
|
-
|
160
|
-
it "should use the current locale when setting" do
|
161
|
-
a
|
162
|
-
|
163
|
-
HasLocalizationTable.configure do |c|
|
164
|
-
c.current_locale = Locale.last
|
165
|
-
end
|
166
|
-
|
167
|
-
a.name = "French Name"
|
168
|
-
a.description = "French Description"
|
169
|
-
|
170
|
-
eng = a.localizations.detect{ |s| s.locale_id == Locale.first.id }
|
171
|
-
fre = a.localizations.detect{ |s| s.locale_id == Locale.last.id }
|
172
|
-
|
173
|
-
eng.name.must_equal "Test"
|
174
|
-
eng.description.must_equal "Description"
|
175
|
-
fre.name.must_equal "French Name"
|
176
|
-
fre.description.must_equal "French Description"
|
177
|
-
end
|
178
|
-
|
179
|
-
it "should create finder methods" do
|
180
|
-
a.save!
|
181
|
-
Article.find_by_name("Test").must_equal a
|
182
|
-
Article.find_by_description("Description").must_equal a
|
183
|
-
Article.find_by_name_and_description("Test", "Description").must_equal a
|
184
|
-
Article.find_by_description_and_name("Description", "Test").must_equal a
|
185
|
-
|
186
|
-
Article.find_by_name("Wrong").must_be_nil
|
187
|
-
Article.find_by_description("Wrong").must_be_nil
|
188
|
-
end
|
189
|
-
|
190
|
-
it "should create ordered_by methods" do
|
191
|
-
a.save!
|
192
|
-
b = Article.create!(name: "Name", description: "Another Description")
|
193
|
-
c = Article.create!(name: "Once Upon a Time...", description: "Fairytale")
|
194
|
-
Article.ordered_by_name.must_equal [b, c, a]
|
195
|
-
Article.ordered_by_description.must_equal [b, a, c]
|
196
|
-
end
|
197
50
|
end
|
198
51
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Setup in-memory database so AR can work
|
2
|
+
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
3
|
+
ActiveRecord::Migration.tap do |m|
|
4
|
+
m.create_table :articles do |t|
|
5
|
+
t.timestamps
|
6
|
+
end
|
7
|
+
|
8
|
+
m.create_table :locales do |t|
|
9
|
+
t.string :name
|
10
|
+
end
|
11
|
+
|
12
|
+
m.create_table :article_localizations do |t|
|
13
|
+
t.integer :article_id
|
14
|
+
t.integer :locale_id
|
15
|
+
t.string :name
|
16
|
+
t.string :description
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set up locales
|
21
|
+
Locale = Class.new(ActiveRecord::Base)
|
22
|
+
Locale.create!(name: "English")
|
23
|
+
Locale.create!(name: "French")
|
24
|
+
|
25
|
+
ArticleLocalization = Class.new(ActiveRecord::Base) do
|
26
|
+
belongs_to :article
|
27
|
+
belongs_to :locale
|
28
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: has_localization_table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2012-08-16 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
requirement: &
|
16
|
+
requirement: &10582840 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.0.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *10582840
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: activerecord
|
27
|
-
requirement: &
|
27
|
+
requirement: &10581980 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 3.0.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *10581980
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: minitest
|
38
|
-
requirement: &
|
38
|
+
requirement: &10581320 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *10581320
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sqlite3
|
49
|
-
requirement: &
|
49
|
+
requirement: &10580720 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *10580720
|
58
58
|
description: Automatically sets up usage of a relational table to contain user-created
|
59
59
|
multi-locale string attributes
|
60
60
|
email:
|
@@ -71,12 +71,23 @@ files:
|
|
71
71
|
- has_localization_table.gemspec
|
72
72
|
- lib/has_localization_table.rb
|
73
73
|
- lib/has_localization_table/active_record.rb
|
74
|
-
- lib/has_localization_table/
|
74
|
+
- lib/has_localization_table/active_record/attributes.rb
|
75
|
+
- lib/has_localization_table/active_record/callbacks.rb
|
76
|
+
- lib/has_localization_table/active_record/finder_methods.rb
|
77
|
+
- lib/has_localization_table/active_record/meta_methods.rb
|
78
|
+
- lib/has_localization_table/active_record/ordered_by.rb
|
79
|
+
- lib/has_localization_table/active_record/relation.rb
|
80
|
+
- lib/has_localization_table/active_record/validations.rb
|
75
81
|
- lib/has_localization_table/config.rb
|
76
|
-
- lib/has_localization_table/instance_methods.rb
|
77
82
|
- lib/has_localization_table/version.rb
|
83
|
+
- spec/active_record/attributes_spec.rb
|
84
|
+
- spec/active_record/finder_method_spec.rb
|
85
|
+
- spec/active_record/ordered_by_spec.rb
|
86
|
+
- spec/active_record/relation_spec.rb
|
87
|
+
- spec/active_record/validation_spec.rb
|
78
88
|
- spec/active_record_spec.rb
|
79
89
|
- spec/spec_helper.rb
|
90
|
+
- spec/support/setup.rb
|
80
91
|
homepage: https://github.com/dvandersluis/has_localization_table
|
81
92
|
licenses: []
|
82
93
|
post_install_message:
|
@@ -103,6 +114,12 @@ specification_version: 3
|
|
103
114
|
summary: Sets up associations and attribute methods for AR models that have a relational
|
104
115
|
table to contain user-created data in multiple languages.
|
105
116
|
test_files:
|
117
|
+
- spec/active_record/attributes_spec.rb
|
118
|
+
- spec/active_record/finder_method_spec.rb
|
119
|
+
- spec/active_record/ordered_by_spec.rb
|
120
|
+
- spec/active_record/relation_spec.rb
|
121
|
+
- spec/active_record/validation_spec.rb
|
106
122
|
- spec/active_record_spec.rb
|
107
123
|
- spec/spec_helper.rb
|
124
|
+
- spec/support/setup.rb
|
108
125
|
has_rdoc:
|
@@ -1,127 +0,0 @@
|
|
1
|
-
module HasLocalizationTable
|
2
|
-
module ClassMethods
|
3
|
-
def self.extended(klass)
|
4
|
-
klass.class_eval do
|
5
|
-
create_localization_association!
|
6
|
-
|
7
|
-
# Initialize string records after main record initialization
|
8
|
-
after_initialize do
|
9
|
-
build_missing_localizations!
|
10
|
-
end
|
11
|
-
|
12
|
-
before_validation do
|
13
|
-
reject_empty_localizations!
|
14
|
-
build_missing_localizations!
|
15
|
-
end
|
16
|
-
|
17
|
-
# Reject any blank strings before saving the record
|
18
|
-
# Validation will have happened by this point, so if there is a required string that is needed, it won't be rejected
|
19
|
-
before_save do
|
20
|
-
reject_empty_localizations!
|
21
|
-
end
|
22
|
-
|
23
|
-
# Add validation to ensure a string for the primary locale exists if the string is required
|
24
|
-
validate do
|
25
|
-
if localization_table_options[:required] || false
|
26
|
-
errors.add(localization_association_name, :primary_lang_string_required) unless localization_association.any? do |string|
|
27
|
-
string.send(HasLocalizationTable.locale_foreign_key) == HasLocalizationTable.primary_locale.id
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
klass.localized_attributes.each do |attribute|
|
34
|
-
# Add validation to make all string fields required for the primary locale
|
35
|
-
klass.send(:localization_class).class_eval do
|
36
|
-
validates attribute, presence: { message: :custom_this_field_is_required },
|
37
|
-
if: proc { |model| klass.name.constantize.localized_attribute_required?(attribute) && model.send(HasLocalizationTable.locale_foreign_key) == HasLocalizationTable.current_locale.id }
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# Alias the scoping method to use the actual association name
|
42
|
-
alias_method :"with_#{klass.localization_association_name}", :with_localizations
|
43
|
-
end
|
44
|
-
|
45
|
-
def localization_class
|
46
|
-
localization_table_options[:class_name].constantize
|
47
|
-
end
|
48
|
-
|
49
|
-
def localization_association_name
|
50
|
-
localization_table_options[:association_name]
|
51
|
-
end
|
52
|
-
|
53
|
-
def localized_attributes
|
54
|
-
# Determine which attributes of the association model should be accessable through the base class
|
55
|
-
# ie. everything that's not a primary key, foreign key, or timestamp attribute
|
56
|
-
association_name = self.localization_table_options[:association_name] || :strings
|
57
|
-
association = reflect_on_association(association_name)
|
58
|
-
|
59
|
-
attribute_names = association.klass.attribute_names
|
60
|
-
timestamp_attrs = association.klass.new.send(:all_timestamp_attributes_in_model).map(&:to_s)
|
61
|
-
foreign_keys = association.klass.reflect_on_all_associations.map{ |a| a.association_foreign_key }
|
62
|
-
primary_keys = [association.klass.primary_key]
|
63
|
-
# protected_attrs = association.klass.protected_attributes.to_a
|
64
|
-
|
65
|
-
(attribute_names - timestamp_attrs - foreign_keys - primary_keys).map(&:to_sym)
|
66
|
-
end
|
67
|
-
|
68
|
-
def localized_attribute_required?(attribute)
|
69
|
-
return false unless localization_table_options[:required] || false
|
70
|
-
return true unless localization_table_options[:optional]
|
71
|
-
|
72
|
-
!localization_table_options[:optional].include?(attribute)
|
73
|
-
end
|
74
|
-
|
75
|
-
def method_missing(name, *args, &block)
|
76
|
-
if name.to_s =~ /\Afind_by_([a-z0-9_]+(_and_[a-z0-9_]+)*)\Z/
|
77
|
-
attributes = $1.split("_and_").map(&:to_sym)
|
78
|
-
if (attributes & localized_attributes).size == attributes.size
|
79
|
-
raise ArgumentError, "expected #{attributes.size} #{"argument".pluralize(attributes.size)}: #{attributes.join(", ")}" unless args.size == attributes.size
|
80
|
-
args = attributes.zip(args).inject({}) { |memo, (key, val)| memo[key] = val; memo }
|
81
|
-
return find_by_localized_attributes(args)
|
82
|
-
end
|
83
|
-
elsif name.to_s =~ /\Aordered_by_([a-z0-9_]+)\Z/
|
84
|
-
attribute = $1.to_sym
|
85
|
-
return ordered_by_localized_attribute(attribute, *args) if localized_attributes.include?(attribute)
|
86
|
-
end
|
87
|
-
|
88
|
-
super
|
89
|
-
end
|
90
|
-
|
91
|
-
def respond_to?(*args)
|
92
|
-
if args.first.to_s =~ /\Afind_by_([a-z0-9_]+(_and_[a-z0-9_]+)*)\Z/
|
93
|
-
attributes = $1.split("_and_").map(&:to_sym)
|
94
|
-
return true if (attributes & localized_attributes).size == attributes.size
|
95
|
-
elsif name.to_s =~ /\Aordered_by_([a-z0-9_]+)\Z/
|
96
|
-
return true if localized_attributes.include?($1.to_sym)
|
97
|
-
end
|
98
|
-
|
99
|
-
super
|
100
|
-
end
|
101
|
-
|
102
|
-
def with_localizations
|
103
|
-
lcat = localization_class.arel_table
|
104
|
-
|
105
|
-
scoped.joins(
|
106
|
-
arel_table.join(lcat, Arel::Nodes::OuterJoin).
|
107
|
-
on(lcat[:"#{self.name.underscore}_id"].eq(arel_table[self.primary_key]).and(lcat[HasLocalizationTable.locale_foreign_key].eq(HasLocalizationTable.current_locale.id))).
|
108
|
-
join_sql
|
109
|
-
)
|
110
|
-
end
|
111
|
-
|
112
|
-
private
|
113
|
-
def create_localization_association!
|
114
|
-
self.has_many localization_association_name, localization_table_options.except(:association_name, :required, :optional)
|
115
|
-
end
|
116
|
-
|
117
|
-
# Find a record by multiple localization values
|
118
|
-
def find_by_localized_attributes(attributes, locale = HasLocalizationTable.current_locale)
|
119
|
-
with_localizations.where(localization_class.table_name => attributes).first
|
120
|
-
end
|
121
|
-
|
122
|
-
# Order records by localization value
|
123
|
-
def ordered_by_localized_attribute(attribute, asc = true, locale = HasLocalizationTable.current_locale)
|
124
|
-
with_localizations.order("#{localization_class.table_name}.#{attribute} #{asc ? "ASC" : "DESC"}")
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
module HasLocalizationTable
|
2
|
-
module InstanceMethods
|
3
|
-
def read_localized_attribute(attribute, locale = HasLocalizationTable.current_locale)
|
4
|
-
attribute_cache[attribute.to_sym][locale.id] ||= localization_association.detect{ |a| a.send(HasLocalizationTable.locale_foreign_key) == locale.id }.send(attribute) rescue nil
|
5
|
-
end
|
6
|
-
|
7
|
-
def write_localized_attribute(attribute, value, locale = HasLocalizationTable.current_locale)
|
8
|
-
value = value.to_s
|
9
|
-
localization = localization_association.detect{ |a| a.send(HasLocalizationTable.locale_foreign_key) == locale.id } ||
|
10
|
-
localization_association.build(HasLocalizationTable.locale_foreign_key => locale.id)
|
11
|
-
|
12
|
-
localization.send(:"#{attribute}=", value)
|
13
|
-
attribute_cache[attribute.to_sym][locale.id] = value.blank? ? nil : value
|
14
|
-
end
|
15
|
-
|
16
|
-
# Define attribute getters and setters
|
17
|
-
def method_missing(name, *args, &block)
|
18
|
-
if name.to_s =~ /\A([a-z0-9_]+)(=)?\Z/i
|
19
|
-
if localized_attributes.include?($1.to_sym)
|
20
|
-
if $2.nil? # No equals sign -- not a setter
|
21
|
-
# Try to load a string for the given locale
|
22
|
-
# If that fails, try for the primary locale
|
23
|
-
raise ArgumentError, "wrong number of arguments (#{args.size} for 0 or 1)" unless args.size.between?(0, 1)
|
24
|
-
return read_localized_attribute($1, args.first) || read_localized_attribute($1, HasLocalizationTable.primary_locale)
|
25
|
-
else
|
26
|
-
raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" unless args.size == 1
|
27
|
-
return write_localized_attribute($1, args.first)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
super
|
33
|
-
end
|
34
|
-
|
35
|
-
def respond_to?(*args)
|
36
|
-
return true if args.first.to_s =~ /\A([a-z0-9_]+)=?\Z/i and localized_attributes.include?($1.to_sym)
|
37
|
-
super
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
# Add localization objects for any available locale that doesn't have one
|
42
|
-
def build_missing_localizations!
|
43
|
-
locale_ids = HasLocalizationTable.all_locales.map(&:id)
|
44
|
-
HasLocalizationTable.all_locales.each do |l|
|
45
|
-
unless localization_association.detect{ |str| str.send(HasLocalizationTable.locale_foreign_key) == l.id }
|
46
|
-
localization_association.build(HasLocalizationTable.locale_foreign_key => l.id)
|
47
|
-
end
|
48
|
-
|
49
|
-
localization_association.sort_by!{ |l| locale_ids.index(l.send(HasLocalizationTable.locale_foreign_key)) || 0 }
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Remove localization objects that are not filled in
|
54
|
-
def reject_empty_localizations!
|
55
|
-
localization_association.reject! { |l| !l.persisted? and localized_attributes.all?{ |attr| l.send(attr).blank? } }
|
56
|
-
end
|
57
|
-
|
58
|
-
# Helper method for getting the localization association without having to look up the name each time
|
59
|
-
def localization_association
|
60
|
-
@localization_association ||= begin
|
61
|
-
association_name = localization_table_options[:association_name]
|
62
|
-
send(association_name)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def attribute_cache
|
67
|
-
@localized_attribute_cache ||= localized_attributes.inject({}) { |memo, attr| memo[attr] = {}; memo }
|
68
|
-
end
|
69
|
-
|
70
|
-
def localized_attributes
|
71
|
-
self.class.localized_attributes
|
72
|
-
end
|
73
|
-
|
74
|
-
def localization_table_options
|
75
|
-
self.class.localization_table_options
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|