globalize 5.0.1 → 5.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.
- checksums.yaml +5 -5
- data/Appraisals +33 -0
- data/CHANGELOG.md +12 -0
- data/CONTRIBUTING.md +15 -0
- data/Gemfile +0 -9
- data/Gemfile.lock +59 -241
- data/{readme.md → README.md} +124 -36
- data/Rakefile +33 -0
- data/lib/globalize/active_record/act_macro.rb +27 -1
- data/lib/globalize/active_record/adapter.rb +13 -5
- data/lib/globalize/active_record/adapter_dirty.rb +56 -0
- data/lib/globalize/active_record/class_methods.rb +23 -9
- data/lib/globalize/active_record/exceptions.rb +1 -7
- data/lib/globalize/active_record/instance_methods.rb +89 -41
- data/lib/globalize/active_record/migration.rb +58 -31
- data/lib/globalize/active_record/translated_attributes_query.rb +181 -0
- data/lib/globalize/active_record.rb +10 -9
- data/lib/globalize/version.rb +1 -1
- data/lib/globalize.rb +20 -6
- data/lib/patches/active_record/persistence.rb +6 -15
- data/lib/patches/active_record/query_method.rb +2 -34
- data/lib/patches/active_record/rails4/query_method.rb +35 -0
- data/lib/patches/active_record/rails4/serialization.rb +22 -0
- data/lib/patches/active_record/rails4/uniqueness_validator.rb +42 -0
- data/lib/patches/active_record/rails5/uniqueness_validator.rb +47 -0
- data/lib/patches/active_record/rails5_1/serialization.rb +22 -0
- data/lib/patches/active_record/rails5_1/uniqueness_validator.rb +45 -0
- data/lib/patches/active_record/relation.rb +12 -0
- data/lib/patches/active_record/serialization.rb +5 -24
- data/lib/patches/active_record/uniqueness_validator.rb +7 -39
- data/lib/patches/active_record/xml_attribute_serializer.rb +19 -9
- metadata +85 -22
- data/globalize.gemspec +0 -29
- data/lib/globalize/active_record/query_methods.rb +0 -98
|
@@ -3,20 +3,18 @@ require 'digest/sha1'
|
|
|
3
3
|
module Globalize
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
module Migration
|
|
6
|
-
attr_reader :globalize_migrator
|
|
7
|
-
|
|
8
6
|
def globalize_migrator
|
|
9
7
|
@globalize_migrator ||= Migrator.new(self)
|
|
10
8
|
end
|
|
11
9
|
|
|
12
|
-
delegate :create_translation_table!, :add_translation_fields!,
|
|
13
|
-
:
|
|
14
|
-
:to => :globalize_migrator
|
|
10
|
+
delegate :create_translation_table!, :add_translation_fields!,
|
|
11
|
+
:drop_translation_table!, :translation_index_name,
|
|
12
|
+
:translation_locale_index_name, :to => :globalize_migrator
|
|
15
13
|
|
|
16
14
|
class Migrator
|
|
17
15
|
include Globalize::ActiveRecord::Exceptions
|
|
18
16
|
|
|
19
|
-
attr_reader :model
|
|
17
|
+
attr_reader :model
|
|
20
18
|
delegate :translated_attribute_names, :connection, :table_name,
|
|
21
19
|
:table_name_prefix, :translations_table_name, :columns, :to => :model
|
|
22
20
|
|
|
@@ -24,7 +22,15 @@ module Globalize
|
|
|
24
22
|
@model = model
|
|
25
23
|
end
|
|
26
24
|
|
|
25
|
+
def fields
|
|
26
|
+
@fields ||= complete_translated_fields
|
|
27
|
+
end
|
|
28
|
+
|
|
27
29
|
def create_translation_table!(fields = {}, options = {})
|
|
30
|
+
extra = options.keys - [:migrate_data, :remove_source_columns, :unique_index]
|
|
31
|
+
if extra.any?
|
|
32
|
+
raise ArgumentError, "Unknown migration #{'option'.pluralize(extra.size)}: #{extra}"
|
|
33
|
+
end
|
|
28
34
|
@fields = fields
|
|
29
35
|
# If we have fields we only want to create the translation table with those fields
|
|
30
36
|
complete_translated_fields if fields.blank?
|
|
@@ -32,14 +38,13 @@ module Globalize
|
|
|
32
38
|
|
|
33
39
|
create_translation_table
|
|
34
40
|
add_translation_fields!(fields, options)
|
|
35
|
-
create_translations_index
|
|
41
|
+
create_translations_index(options)
|
|
36
42
|
clear_schema_cache!
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
def add_translation_fields!(fields, options = {})
|
|
40
46
|
@fields = fields
|
|
41
47
|
validate_translated_fields
|
|
42
|
-
|
|
43
48
|
add_translation_fields
|
|
44
49
|
clear_schema_cache!
|
|
45
50
|
move_data_to_translation_table if options[:migrate_data]
|
|
@@ -48,7 +53,12 @@ module Globalize
|
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
def remove_source_columns
|
|
51
|
-
|
|
56
|
+
column_names = *fields.keys
|
|
57
|
+
column_names.each do |column|
|
|
58
|
+
if connection.column_exists?(table_name, column)
|
|
59
|
+
connection.remove_column(table_name, column)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
52
62
|
end
|
|
53
63
|
|
|
54
64
|
def drop_translation_table!(options = {})
|
|
@@ -62,13 +72,13 @@ module Globalize
|
|
|
62
72
|
# It's a problem because in early migrations would add all the translated attributes
|
|
63
73
|
def complete_translated_fields
|
|
64
74
|
translated_attribute_names.each do |name|
|
|
65
|
-
fields[name] ||= column_type(name)
|
|
75
|
+
@fields[name] ||= column_type(name)
|
|
66
76
|
end
|
|
67
77
|
end
|
|
68
78
|
|
|
69
79
|
def create_translation_table
|
|
70
80
|
connection.create_table(translations_table_name) do |t|
|
|
71
|
-
t.references table_name.sub(/^#{table_name_prefix}/, '').singularize, :null => false
|
|
81
|
+
t.references table_name.sub(/^#{table_name_prefix}/, '').singularize, :null => false, :index => false, :type => column_type(model.primary_key).to_sym
|
|
72
82
|
t.string :locale, :null => false
|
|
73
83
|
t.timestamps :null => false
|
|
74
84
|
end
|
|
@@ -86,10 +96,11 @@ module Globalize
|
|
|
86
96
|
end
|
|
87
97
|
end
|
|
88
98
|
|
|
89
|
-
def create_translations_index
|
|
99
|
+
def create_translations_index(options)
|
|
100
|
+
foreign_key = "#{table_name.sub(/^#{table_name_prefix}/, "").singularize}_id".to_sym
|
|
90
101
|
connection.add_index(
|
|
91
102
|
translations_table_name,
|
|
92
|
-
|
|
103
|
+
foreign_key,
|
|
93
104
|
:name => translation_index_name
|
|
94
105
|
)
|
|
95
106
|
# index for select('DISTINCT locale') call in translation.rb
|
|
@@ -98,6 +109,15 @@ module Globalize
|
|
|
98
109
|
:locale,
|
|
99
110
|
:name => translation_locale_index_name
|
|
100
111
|
)
|
|
112
|
+
|
|
113
|
+
if options[:unique_index]
|
|
114
|
+
connection.add_index(
|
|
115
|
+
translations_table_name,
|
|
116
|
+
[foreign_key, :locale],
|
|
117
|
+
:name => translation_unique_index_name,
|
|
118
|
+
unique: true
|
|
119
|
+
)
|
|
120
|
+
end
|
|
101
121
|
end
|
|
102
122
|
|
|
103
123
|
def drop_translation_table
|
|
@@ -105,12 +125,17 @@ module Globalize
|
|
|
105
125
|
end
|
|
106
126
|
|
|
107
127
|
def drop_translations_index
|
|
108
|
-
connection.
|
|
128
|
+
if connection.indexes(translations_table_name).map(&:name).include?(translation_index_name)
|
|
129
|
+
connection.remove_index(translations_table_name, :name => translation_index_name)
|
|
130
|
+
end
|
|
131
|
+
if connection.indexes(translations_table_name).map(&:name).include?(translation_locale_index_name)
|
|
132
|
+
connection.remove_index(translations_table_name, :name => translation_locale_index_name)
|
|
133
|
+
end
|
|
109
134
|
end
|
|
110
135
|
|
|
111
136
|
def move_data_to_translation_table
|
|
112
137
|
model.find_each do |record|
|
|
113
|
-
translation = record.translation_for(I18n.
|
|
138
|
+
translation = record.translation_for(I18n.locale) || record.translations.build(:locale => I18n.locale)
|
|
114
139
|
fields.each do |attribute_name, attribute_type|
|
|
115
140
|
translation[attribute_name] = record.read_attribute(attribute_name, {:translated => false})
|
|
116
141
|
end
|
|
@@ -122,7 +147,7 @@ module Globalize
|
|
|
122
147
|
add_missing_columns
|
|
123
148
|
|
|
124
149
|
# Find all of the translated attributes for all records in the model.
|
|
125
|
-
all_translated_attributes =
|
|
150
|
+
all_translated_attributes = model.all.collect{|m| m.attributes}
|
|
126
151
|
all_translated_attributes.each do |translated_record|
|
|
127
152
|
# Create a hash containing the translated column names and their values.
|
|
128
153
|
translated_attribute_names.inject(fields_to_update={}) do |f, name|
|
|
@@ -130,40 +155,34 @@ module Globalize
|
|
|
130
155
|
end
|
|
131
156
|
|
|
132
157
|
# Now, update the actual model's record with the hash.
|
|
133
|
-
|
|
158
|
+
model.where(model.primary_key.to_sym => translated_record[model.primary_key]).update_all(fields_to_update)
|
|
134
159
|
end
|
|
135
160
|
end
|
|
136
161
|
|
|
137
162
|
def validate_translated_fields
|
|
138
163
|
fields.each do |name, options|
|
|
139
164
|
raise BadFieldName.new(name) unless valid_field_name?(name)
|
|
140
|
-
type = (options.is_a? Hash) ? options[:type] : options
|
|
141
|
-
raise BadFieldType.new(name, type) unless valid_field_type?(name, type)
|
|
142
165
|
end
|
|
143
166
|
end
|
|
144
167
|
|
|
145
168
|
def column_type(name)
|
|
146
|
-
columns.detect { |c| c.name == name.to_s }.try(:type)
|
|
169
|
+
columns.detect { |c| c.name == name.to_s }.try(:type) || :string
|
|
147
170
|
end
|
|
148
171
|
|
|
149
172
|
def valid_field_name?(name)
|
|
150
173
|
translated_attribute_names.include?(name)
|
|
151
174
|
end
|
|
152
175
|
|
|
153
|
-
def valid_field_type?(name, type)
|
|
154
|
-
!translated_attribute_names.include?(name) || [:string, :text].include?(type)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
176
|
def translation_index_name
|
|
158
|
-
|
|
159
|
-
index_name.size < connection.index_name_length ? index_name :
|
|
160
|
-
"index_#{Digest::SHA1.hexdigest(index_name)}"[0, connection.index_name_length]
|
|
177
|
+
truncate_index_name "index_#{translations_table_name}_on_#{table_name.singularize}_id"
|
|
161
178
|
end
|
|
162
179
|
|
|
163
180
|
def translation_locale_index_name
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
181
|
+
truncate_index_name "index_#{translations_table_name}_on_locale"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def translation_unique_index_name
|
|
185
|
+
truncate_index_name "index_#{translations_table_name}_on_#{table_name.singularize}_id_and_locale"
|
|
167
186
|
end
|
|
168
187
|
|
|
169
188
|
def clear_schema_cache!
|
|
@@ -174,14 +193,22 @@ module Globalize
|
|
|
174
193
|
|
|
175
194
|
private
|
|
176
195
|
|
|
196
|
+
def truncate_index_name(index_name)
|
|
197
|
+
if index_name.size < connection.index_name_length
|
|
198
|
+
index_name
|
|
199
|
+
else
|
|
200
|
+
"index_#{Digest::SHA1.hexdigest(index_name)}"[0, connection.index_name_length]
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
177
204
|
def add_missing_columns
|
|
205
|
+
clear_schema_cache!
|
|
178
206
|
translated_attribute_names.map(&:to_s).each do |attribute|
|
|
179
207
|
unless model.column_names.include?(attribute)
|
|
180
208
|
connection.add_column(table_name, attribute, model::Translation.columns_hash[attribute].type)
|
|
181
209
|
end
|
|
182
210
|
end
|
|
183
211
|
end
|
|
184
|
-
|
|
185
212
|
end
|
|
186
213
|
end
|
|
187
214
|
end
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
module Globalize
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module TranslatedAttributesQuery
|
|
4
|
+
class WhereChain < ::ActiveRecord::QueryMethods::WhereChain
|
|
5
|
+
def not(opts, *rest)
|
|
6
|
+
if parsed = @scope.clone.parse_translated_conditions(opts)
|
|
7
|
+
@scope.join_translations.where.not(parsed, *rest)
|
|
8
|
+
else
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def where(opts = :chain, *rest)
|
|
15
|
+
if opts == :chain
|
|
16
|
+
WhereChain.new(spawn)
|
|
17
|
+
elsif parsed = parse_translated_conditions(opts)
|
|
18
|
+
join_translations(super(parsed, *rest))
|
|
19
|
+
else
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def having(opts, *rest)
|
|
25
|
+
if parsed = parse_translated_conditions(opts)
|
|
26
|
+
join_translations(super(parsed, *rest))
|
|
27
|
+
else
|
|
28
|
+
super
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def order(opts, *rest)
|
|
33
|
+
if respond_to?(:translated_attribute_names) && parsed = parse_translated_order(opts)
|
|
34
|
+
join_translations super(parsed)
|
|
35
|
+
else
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reorder(opts, *rest)
|
|
41
|
+
if respond_to?(:translated_attribute_names) && parsed = parse_translated_order(opts)
|
|
42
|
+
join_translations super(parsed)
|
|
43
|
+
else
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def group(*columns)
|
|
49
|
+
if respond_to?(:translated_attribute_names) && parsed = parse_translated_columns(columns)
|
|
50
|
+
join_translations super(parsed)
|
|
51
|
+
else
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def select(*columns)
|
|
57
|
+
if respond_to?(:translated_attribute_names) && parsed = parse_translated_columns(columns)
|
|
58
|
+
join_translations super(parsed)
|
|
59
|
+
else
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def exists?(conditions = :none)
|
|
65
|
+
if parsed = parse_translated_conditions(conditions)
|
|
66
|
+
with_translations_in_fallbacks.exists?(parsed)
|
|
67
|
+
else
|
|
68
|
+
super
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def calculate(*args)
|
|
73
|
+
column_name = args[1]
|
|
74
|
+
if respond_to?(:translated_attribute_names) && translated_column?(column_name)
|
|
75
|
+
args[1] = translated_column_name(column_name)
|
|
76
|
+
join_translations.calculate(*args)
|
|
77
|
+
else
|
|
78
|
+
super
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def pluck(*column_names)
|
|
83
|
+
if respond_to?(:translated_attribute_names) && parsed = parse_translated_columns(column_names)
|
|
84
|
+
join_translations.pluck(*parsed)
|
|
85
|
+
else
|
|
86
|
+
super
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def with_translations_in_fallbacks
|
|
91
|
+
with_translations(Globalize.fallbacks)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def parse_translated_conditions(opts)
|
|
95
|
+
if opts.is_a?(Hash) && respond_to?(:translated_attribute_names) && (keys = opts.symbolize_keys.keys & translated_attribute_names).present?
|
|
96
|
+
opts = opts.dup
|
|
97
|
+
keys.each { |key| opts[translated_column_name(key)] = opts.delete(key) || opts.delete(key.to_s) }
|
|
98
|
+
opts
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if ::ActiveRecord::VERSION::STRING < "5.0.0"
|
|
103
|
+
def where_values_hash(*args)
|
|
104
|
+
return super unless respond_to?(:translations_table_name)
|
|
105
|
+
equalities = respond_to?(:with_default_scope) ? with_default_scope.where_values : where_values
|
|
106
|
+
equalities = equalities.grep(Arel::Nodes::Equality).find_all { |node|
|
|
107
|
+
node.left.relation.name == translations_table_name
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
|
|
111
|
+
|
|
112
|
+
super.merge(Hash[equalities.map { |where|
|
|
113
|
+
name = where.left.name
|
|
114
|
+
[name, binds.fetch(name.to_s) { right = where.right; right.is_a?(Arel::Nodes::Casted) ? right.val : right }]
|
|
115
|
+
}])
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def join_translations(relation = self)
|
|
120
|
+
if relation.joins_values.include?(:translations)
|
|
121
|
+
relation
|
|
122
|
+
else
|
|
123
|
+
relation.with_translations_in_fallbacks
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def arel_translated_order_node(column, direction)
|
|
130
|
+
unless translated_column?(column)
|
|
131
|
+
return self.arel_table[column].send(direction)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
full_column = translated_column_name(column)
|
|
135
|
+
|
|
136
|
+
# Inject `full_column` to the select values to avoid
|
|
137
|
+
# PG::InvalidColumnReference errors with distinct queries on Postgres
|
|
138
|
+
if select_values.empty?
|
|
139
|
+
self.select_values = [self.arel_table[Arel.star], full_column]
|
|
140
|
+
else
|
|
141
|
+
self.select_values << full_column
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
translation_class.arel_table[column].send(direction)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def parse_translated_order(opts)
|
|
148
|
+
case opts
|
|
149
|
+
when Hash
|
|
150
|
+
# Do not process nothing unless there is at least a translated column
|
|
151
|
+
# so that the `order` statement will be processed by the original
|
|
152
|
+
# ActiveRecord method
|
|
153
|
+
return nil unless opts.find { |col, dir| translated_column?(col) }
|
|
154
|
+
|
|
155
|
+
# Build order arel nodes for translateds and untranslateds statements
|
|
156
|
+
ordering = opts.map do |column, direction|
|
|
157
|
+
arel_translated_order_node(column, direction)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
order(ordering).order_values
|
|
161
|
+
when Symbol
|
|
162
|
+
parse_translated_order({ opts => :asc })
|
|
163
|
+
when Array
|
|
164
|
+
parse_translated_order(Hash[opts.collect { |opt| [opt, :asc] } ])
|
|
165
|
+
else # failsafe returns nothing
|
|
166
|
+
nil
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def parse_translated_columns(columns)
|
|
171
|
+
if columns.is_a?(Array) && (columns.flatten & translated_attribute_names).present?
|
|
172
|
+
columns.flatten.map { |column| translated_column?(column) ? translated_column_name(column) : column }
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def translated_column?(column)
|
|
177
|
+
translated_attribute_names.include?(column)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
module Globalize
|
|
2
2
|
module ActiveRecord
|
|
3
|
-
autoload :ActMacro,
|
|
4
|
-
autoload :Adapter,
|
|
5
|
-
autoload :
|
|
6
|
-
autoload :
|
|
7
|
-
autoload :
|
|
8
|
-
autoload :
|
|
9
|
-
autoload :
|
|
10
|
-
autoload :
|
|
11
|
-
autoload :
|
|
3
|
+
autoload :ActMacro, 'globalize/active_record/act_macro'
|
|
4
|
+
autoload :Adapter, 'globalize/active_record/adapter'
|
|
5
|
+
autoload :AdapterDirty, 'globalize/active_record/adapter_dirty'
|
|
6
|
+
autoload :Attributes, 'globalize/active_record/attributes'
|
|
7
|
+
autoload :ClassMethods, 'globalize/active_record/class_methods'
|
|
8
|
+
autoload :Exceptions, 'globalize/active_record/exceptions'
|
|
9
|
+
autoload :InstanceMethods, 'globalize/active_record/instance_methods'
|
|
10
|
+
autoload :Migration, 'globalize/active_record/migration'
|
|
11
|
+
autoload :Translation, 'globalize/active_record/translation'
|
|
12
|
+
autoload :TranslatedAttributesQuery, 'globalize/active_record/translated_attributes_query'
|
|
12
13
|
end
|
|
13
14
|
end
|
data/lib/globalize/version.rb
CHANGED
data/lib/globalize.rb
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
require 'request_store'
|
|
1
2
|
require 'active_record'
|
|
2
3
|
require 'patches/active_record/xml_attribute_serializer'
|
|
3
4
|
require 'patches/active_record/query_method'
|
|
5
|
+
require 'patches/active_record/relation'
|
|
4
6
|
require 'patches/active_record/serialization'
|
|
5
7
|
require 'patches/active_record/uniqueness_validator'
|
|
6
8
|
require 'patches/active_record/persistence'
|
|
7
9
|
|
|
8
|
-
|
|
9
10
|
module Globalize
|
|
10
11
|
autoload :ActiveRecord, 'globalize/active_record'
|
|
11
12
|
autoload :Interpolation, 'globalize/interpolation'
|
|
@@ -52,18 +53,31 @@ module Globalize
|
|
|
52
53
|
i18n_fallbacks? ? I18n.fallbacks[for_locale] : [for_locale.to_sym]
|
|
53
54
|
end
|
|
54
55
|
|
|
56
|
+
# Thread-safe global storage
|
|
57
|
+
def storage
|
|
58
|
+
RequestStore.store
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def rails_5?
|
|
62
|
+
::ActiveRecord.version >= Gem::Version.new('5.1.0')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def rails_52?
|
|
66
|
+
::ActiveRecord.version >= Gem::Version.new('5.2.0')
|
|
67
|
+
end
|
|
68
|
+
|
|
55
69
|
protected
|
|
56
70
|
|
|
57
71
|
def read_locale
|
|
58
|
-
|
|
72
|
+
storage[:globalize_locale]
|
|
59
73
|
end
|
|
60
74
|
|
|
61
75
|
def set_locale(locale)
|
|
62
|
-
|
|
76
|
+
storage[:globalize_locale] = locale.try(:to_sym)
|
|
63
77
|
end
|
|
64
78
|
|
|
65
79
|
def read_fallbacks
|
|
66
|
-
|
|
80
|
+
storage[:globalize_fallbacks] || HashWithIndifferentAccess.new
|
|
67
81
|
end
|
|
68
82
|
|
|
69
83
|
def set_fallbacks(locales)
|
|
@@ -73,12 +87,12 @@ module Globalize
|
|
|
73
87
|
fallback_hash[key] = value.presence || [key]
|
|
74
88
|
end if locales.present?
|
|
75
89
|
|
|
76
|
-
|
|
90
|
+
storage[:globalize_fallbacks] = fallback_hash
|
|
77
91
|
end
|
|
78
92
|
end
|
|
79
93
|
end
|
|
80
94
|
|
|
81
|
-
ActiveRecord::Base.
|
|
95
|
+
ActiveRecord::Base.class_attribute :globalize_serialized_attributes, instance_writer: false
|
|
82
96
|
ActiveRecord::Base.globalize_serialized_attributes = {}
|
|
83
97
|
|
|
84
98
|
ActiveRecord::Base.extend(Globalize::ActiveRecord::ActMacro)
|
|
@@ -1,26 +1,17 @@
|
|
|
1
|
-
module
|
|
1
|
+
module Globalize
|
|
2
2
|
module Persistence
|
|
3
3
|
# Updates the associated record with values matching those of the instance attributes.
|
|
4
4
|
# Returns the number of affected rows.
|
|
5
5
|
def _update_record(attribute_names = self.attribute_names)
|
|
6
6
|
attribute_names_without_translated = attribute_names.select{ |k| not respond_to?('translated?') or not translated?(k) }
|
|
7
|
-
|
|
8
|
-
if attributes_values.empty?
|
|
9
|
-
0
|
|
10
|
-
else
|
|
11
|
-
self.class.unscoped._update_record attributes_values, id, id_was
|
|
12
|
-
end
|
|
7
|
+
super(attribute_names_without_translated)
|
|
13
8
|
end
|
|
14
9
|
|
|
15
10
|
def _create_record(attribute_names = self.attribute_names)
|
|
16
11
|
attribute_names_without_translated = attribute_names.select{ |k| not respond_to?('translated?') or not translated?(k) }
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
new_id = self.class.unscoped.insert attributes_values
|
|
20
|
-
self.id ||= new_id if self.class.primary_key
|
|
21
|
-
|
|
22
|
-
@new_record = false
|
|
23
|
-
id
|
|
12
|
+
super(attribute_names_without_translated)
|
|
24
13
|
end
|
|
25
14
|
end
|
|
26
|
-
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
ActiveRecord::Persistence.send(:prepend, Globalize::Persistence)
|
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module ActiveRecord
|
|
4
|
-
module AttributeMethods
|
|
5
|
-
module Query
|
|
6
|
-
def query_attribute(attr_name)
|
|
7
|
-
unless value = read_attribute(attr_name)
|
|
8
|
-
false
|
|
9
|
-
else
|
|
10
|
-
column = self.class.columns_hash[attr_name]
|
|
11
|
-
if column.nil?
|
|
12
|
-
|
|
13
|
-
# TODO submit a rails patch
|
|
14
|
-
|
|
15
|
-
# not sure what active_record tests say but i guess this should mean:
|
|
16
|
-
# call to_i and check zero? if the value is a Numeric or starts with
|
|
17
|
-
# a digit, so it can meaningfully be typecasted by to_i
|
|
18
|
-
|
|
19
|
-
# if Numeric === value || value !~ /[^0-9]/
|
|
20
|
-
if Numeric === value || value.to_s =~ /^[0-9]/
|
|
21
|
-
!value.to_i.zero?
|
|
22
|
-
else
|
|
23
|
-
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
|
|
24
|
-
!value.blank?
|
|
25
|
-
end
|
|
26
|
-
elsif column.number?
|
|
27
|
-
!value.zero?
|
|
28
|
-
else
|
|
29
|
-
!value.blank?
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
1
|
+
if ::ActiveRecord::VERSION::STRING < "5.0.0"
|
|
2
|
+
require_relative 'rails4/query_method'
|
|
35
3
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'active_record/attribute_methods/query'
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module AttributeMethods
|
|
5
|
+
module Query
|
|
6
|
+
def query_attribute(attr_name)
|
|
7
|
+
unless value = read_attribute(attr_name)
|
|
8
|
+
false
|
|
9
|
+
else
|
|
10
|
+
column = self.class.columns_hash[attr_name]
|
|
11
|
+
if column.nil?
|
|
12
|
+
|
|
13
|
+
# TODO submit a rails patch
|
|
14
|
+
|
|
15
|
+
# not sure what active_record tests say but i guess this should mean:
|
|
16
|
+
# call to_i and check zero? if the value is a Numeric or starts with
|
|
17
|
+
# a digit, so it can meaningfully be typecasted by to_i
|
|
18
|
+
|
|
19
|
+
# if Numeric === value || value !~ /[^0-9]/
|
|
20
|
+
if Numeric === value || value.to_s =~ /^[0-9]/
|
|
21
|
+
!value.to_i.zero?
|
|
22
|
+
else
|
|
23
|
+
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
|
|
24
|
+
!value.blank?
|
|
25
|
+
end
|
|
26
|
+
elsif column.number?
|
|
27
|
+
!value.zero?
|
|
28
|
+
else
|
|
29
|
+
!value.blank?
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Globalize
|
|
2
|
+
module AttributeMethods
|
|
3
|
+
module Serialization
|
|
4
|
+
def serialize(attr_name, class_name_or_coder = Object)
|
|
5
|
+
super(attr_name, class_name_or_coder)
|
|
6
|
+
|
|
7
|
+
coder = if class_name_or_coder == ::JSON
|
|
8
|
+
::ActiveRecord::Coders::JSON
|
|
9
|
+
elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
|
|
10
|
+
class_name_or_coder
|
|
11
|
+
else
|
|
12
|
+
::ActiveRecord::Coders::YAMLColumn.new(class_name_or_coder)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
self.globalize_serialized_attributes = globalize_serialized_attributes.dup
|
|
16
|
+
self.globalize_serialized_attributes[attr_name] = coder
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
ActiveRecord::AttributeMethods::Serialization::ClassMethods.send(:prepend, Globalize::AttributeMethods::Serialization)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'active_record/validations/uniqueness.rb'
|
|
2
|
+
|
|
3
|
+
module Globalize
|
|
4
|
+
module UniquenessValidatorOverride
|
|
5
|
+
def validate_each(record, attribute, value)
|
|
6
|
+
klass = record.class
|
|
7
|
+
if klass.translates? && klass.translated?(attribute)
|
|
8
|
+
finder_class = klass.translation_class
|
|
9
|
+
table = finder_class.arel_table
|
|
10
|
+
|
|
11
|
+
relation = build_relation(finder_class, table, attribute, value).and(table[:locale].eq(Globalize.locale))
|
|
12
|
+
relation = relation.and(table[klass.reflect_on_association(:translations).foreign_key].not_eq(record.send(:id))) if record.persisted?
|
|
13
|
+
|
|
14
|
+
translated_scopes = Array(options[:scope]) & klass.translated_attribute_names
|
|
15
|
+
untranslated_scopes = Array(options[:scope]) - translated_scopes
|
|
16
|
+
|
|
17
|
+
untranslated_scopes.each do |scope_item|
|
|
18
|
+
scope_value = record.send(scope_item)
|
|
19
|
+
reflection = klass.reflect_on_association(scope_item)
|
|
20
|
+
if reflection
|
|
21
|
+
scope_value = record.send(reflection.foreign_key)
|
|
22
|
+
scope_item = reflection.foreign_key
|
|
23
|
+
end
|
|
24
|
+
relation = relation.and(find_finder_class_for(record).arel_table[scope_item].eq(scope_value))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
translated_scopes.each do |scope_item|
|
|
28
|
+
scope_value = record.send(scope_item)
|
|
29
|
+
relation = relation.and(table[scope_item].eq(scope_value))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if klass.unscoped.with_translations.where(relation).exists?
|
|
33
|
+
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
ActiveRecord::Validations::UniquenessValidator.send :prepend, Globalize::UniquenessValidatorOverride
|