globalize 5.0.1 → 5.1.0.beta1
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 +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile +19 -5
- data/Gemfile.lock +97 -47
- data/{readme.md → README.md} +116 -35
- data/Rakefile +33 -0
- data/lib/globalize.rb +11 -5
- data/lib/globalize/active_record.rb +1 -0
- data/lib/globalize/active_record/act_macro.rb +24 -3
- data/lib/globalize/active_record/adapter.rb +10 -11
- data/lib/globalize/active_record/adapter_dirty.rb +54 -0
- data/lib/globalize/active_record/class_methods.rb +15 -6
- data/lib/globalize/active_record/exceptions.rb +1 -7
- data/lib/globalize/active_record/instance_methods.rb +55 -35
- data/lib/globalize/active_record/migration.rb +51 -29
- data/lib/globalize/active_record/query_methods.rb +42 -17
- data/lib/globalize/version.rb +1 -1
- 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/uniqueness_validator.rb +42 -0
- data/lib/patches/active_record/rails5/uniqueness_validator.rb +47 -0
- data/lib/patches/active_record/relation.rb +12 -0
- data/lib/patches/active_record/serialization.rb +13 -16
- data/lib/patches/active_record/uniqueness_validator.rb +5 -39
- data/lib/patches/active_record/xml_attribute_serializer.rb +19 -9
- metadata +27 -24
- data/globalize.gemspec +0 -29
data/Rakefile
CHANGED
@@ -20,3 +20,36 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
20
20
|
rdoc.rdoc_files.include('README')
|
21
21
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
22
|
end
|
23
|
+
|
24
|
+
task :load_path do
|
25
|
+
%w(lib test).each do |path|
|
26
|
+
$LOAD_PATH.unshift(File.expand_path("../#{path}", __FILE__))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
namespace :db do
|
31
|
+
desc 'Create the database'
|
32
|
+
task :create => :load_path do
|
33
|
+
require 'support/database'
|
34
|
+
|
35
|
+
Globalize::Test::Database.create!
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Drop the database"
|
39
|
+
task :drop => :load_path do
|
40
|
+
require 'support/database'
|
41
|
+
|
42
|
+
Globalize::Test::Database.drop!
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Set up the database schema"
|
46
|
+
task :migrate => :load_path do
|
47
|
+
require 'support/database'
|
48
|
+
|
49
|
+
Globalize::Test::Database.migrate!
|
50
|
+
# ActiveRecord::Schema.migrate :up
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Drop and recreate the database schema"
|
54
|
+
task :reset => [:drop, :create]
|
55
|
+
end
|
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,23 @@ 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
|
+
|
55
61
|
protected
|
56
62
|
|
57
63
|
def read_locale
|
58
|
-
|
64
|
+
storage[:globalize_locale]
|
59
65
|
end
|
60
66
|
|
61
67
|
def set_locale(locale)
|
62
|
-
|
68
|
+
storage[:globalize_locale] = locale.try(:to_sym)
|
63
69
|
end
|
64
70
|
|
65
71
|
def read_fallbacks
|
66
|
-
|
72
|
+
storage[:globalize_fallbacks] || HashWithIndifferentAccess.new
|
67
73
|
end
|
68
74
|
|
69
75
|
def set_fallbacks(locales)
|
@@ -73,7 +79,7 @@ module Globalize
|
|
73
79
|
fallback_hash[key] = value.presence || [key]
|
74
80
|
end if locales.present?
|
75
81
|
|
76
|
-
|
82
|
+
storage[:globalize_fallbacks] = fallback_hash
|
77
83
|
end
|
78
84
|
end
|
79
85
|
end
|
@@ -2,6 +2,7 @@ module Globalize
|
|
2
2
|
module ActiveRecord
|
3
3
|
autoload :ActMacro, 'globalize/active_record/act_macro'
|
4
4
|
autoload :Adapter, 'globalize/active_record/adapter'
|
5
|
+
autoload :AdapterDirty, 'globalize/active_record/adapter_dirty'
|
5
6
|
autoload :Attributes, 'globalize/active_record/attributes'
|
6
7
|
autoload :ClassMethods, 'globalize/active_record/class_methods'
|
7
8
|
autoload :Exceptions, 'globalize/active_record/exceptions'
|
@@ -5,6 +5,7 @@ module Globalize
|
|
5
5
|
options = attr_names.extract_options!
|
6
6
|
# Bypass setup_translates! if the initial bootstrapping is done already.
|
7
7
|
setup_translates!(options) unless translates?
|
8
|
+
check_columns!(attr_names)
|
8
9
|
|
9
10
|
# Add any extra translatable attributes.
|
10
11
|
attr_names = attr_names.map(&:to_sym)
|
@@ -38,6 +39,25 @@ module Globalize
|
|
38
39
|
# Add attribute to the list.
|
39
40
|
self.translated_attribute_names << attr_name
|
40
41
|
end
|
42
|
+
|
43
|
+
if ::ActiveRecord::VERSION::STRING > "5.0" && table_exists? && translation_class.table_exists?
|
44
|
+
self.ignored_columns += translated_attribute_names.map(&:to_s)
|
45
|
+
reset_column_information
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_columns!(attr_names)
|
50
|
+
# If tables do not exist, do not warn about conflicting columns
|
51
|
+
return unless table_exists? && translation_class.table_exists?
|
52
|
+
|
53
|
+
if (overlap = attr_names.map(&:to_s) & column_names).present?
|
54
|
+
ActiveSupport::Deprecation.warn(
|
55
|
+
["You have defined one or more translated attributes with names that conflict with column(s) on the model table. ",
|
56
|
+
"Globalize does not support this configuration anymore, remove or rename column(s) on the model table.\n",
|
57
|
+
"Model name (table name): #{model_name} (#{table_name})\n",
|
58
|
+
"Attribute name(s): #{overlap.join(', ')}\n"].join
|
59
|
+
)
|
60
|
+
end
|
41
61
|
end
|
42
62
|
|
43
63
|
def apply_globalize_options(options)
|
@@ -74,10 +94,11 @@ module Globalize
|
|
74
94
|
:foreign_key => options[:foreign_key],
|
75
95
|
:dependent => :destroy,
|
76
96
|
:extend => HasManyExtensions,
|
77
|
-
:autosave =>
|
97
|
+
:autosave => true,
|
98
|
+
:inverse_of => :globalized_model
|
78
99
|
|
79
|
-
|
80
|
-
|
100
|
+
before_create :save_translations!
|
101
|
+
before_update :save_translations!
|
81
102
|
end
|
82
103
|
end
|
83
104
|
|
@@ -3,7 +3,7 @@ module Globalize
|
|
3
3
|
class Adapter
|
4
4
|
# The cache caches attributes that already were looked up for read access.
|
5
5
|
# The stash keeps track of new or changed values that need to be saved.
|
6
|
-
attr_accessor :record, :stash
|
6
|
+
attr_accessor :record, :stash
|
7
7
|
private :record=, :stash=
|
8
8
|
|
9
9
|
delegate :translation_class, :to => :'record.class'
|
@@ -34,16 +34,15 @@ module Globalize
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def save_translations!
|
37
|
-
stash.
|
37
|
+
stash.each do |locale, attrs|
|
38
|
+
next if attrs.empty?
|
39
|
+
|
38
40
|
translation = record.translations_by_locale[locale] ||
|
39
41
|
record.translations.build(locale: locale.to_s)
|
40
|
-
|
41
42
|
attrs.each do |name, value|
|
42
43
|
value = value.val if value.is_a?(Arel::Nodes::Casted)
|
43
44
|
translation[name] = value
|
44
45
|
end
|
45
|
-
ensure_foreign_key_for(translation)
|
46
|
-
translation.save!
|
47
46
|
end
|
48
47
|
|
49
48
|
reset
|
@@ -55,11 +54,6 @@ module Globalize
|
|
55
54
|
|
56
55
|
protected
|
57
56
|
|
58
|
-
# Sometimes the translation is initialised before a foreign key can be set.
|
59
|
-
def ensure_foreign_key_for(translation)
|
60
|
-
translation[translation.class.reflections["globalized_model"].foreign_key] = record.id
|
61
|
-
end
|
62
|
-
|
63
57
|
def type_cast(name, value)
|
64
58
|
return value.presence unless column = column_for_attribute(name)
|
65
59
|
|
@@ -76,7 +70,11 @@ module Globalize
|
|
76
70
|
|
77
71
|
def fetch_attribute(locale, name)
|
78
72
|
translation = record.translation_for(locale, false)
|
79
|
-
|
73
|
+
if translation
|
74
|
+
translation.send(name)
|
75
|
+
else
|
76
|
+
record.class.translation_class.new.send(name)
|
77
|
+
end
|
80
78
|
end
|
81
79
|
|
82
80
|
def set_metadata(object, metadata)
|
@@ -95,6 +93,7 @@ module Globalize
|
|
95
93
|
end
|
96
94
|
|
97
95
|
delegate :fallbacks_for_empty_translations?, :to => :record, :prefix => false
|
96
|
+
prepend AdapterDirty
|
98
97
|
end
|
99
98
|
end
|
100
99
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Globalize
|
2
|
+
module ActiveRecord
|
3
|
+
module AdapterDirty
|
4
|
+
def write locale, name, value
|
5
|
+
# Dirty tracking, paraphrased from
|
6
|
+
# ActiveRecord::AttributeMethods::Dirty#write_attribute.
|
7
|
+
name = name.to_s
|
8
|
+
store_old_value name, locale
|
9
|
+
old_values = dirty[name]
|
10
|
+
old_value = old_values[locale]
|
11
|
+
is_changed = record.send :attribute_changed?, name
|
12
|
+
if is_changed && value == old_value
|
13
|
+
# If there's already a change, delete it if this undoes the change.
|
14
|
+
old_values.delete locale
|
15
|
+
if old_values.empty?
|
16
|
+
_reset_attribute name
|
17
|
+
end
|
18
|
+
elsif !is_changed
|
19
|
+
# If there's not a change yet, record it.
|
20
|
+
record.send(:attribute_will_change!, name) if old_value != value
|
21
|
+
end
|
22
|
+
|
23
|
+
super locale, name, value
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_writer :dirty
|
27
|
+
def dirty
|
28
|
+
@dirty ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def store_old_value name, locale
|
32
|
+
dirty[name] ||= {}
|
33
|
+
unless dirty[name].key? locale
|
34
|
+
old = fetch(locale, name)
|
35
|
+
old = old.dup if old.duplicable?
|
36
|
+
dirty[name][locale] = old
|
37
|
+
end
|
38
|
+
end
|
39
|
+
def clear_dirty
|
40
|
+
self.dirty = {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def _reset_attribute name
|
44
|
+
record.send("#{name}=", record.changed_attributes[name])
|
45
|
+
record.original_changed_attributes.except!(name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def reset
|
49
|
+
clear_dirty
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -3,13 +3,21 @@ module Globalize
|
|
3
3
|
module ClassMethods
|
4
4
|
delegate :translated_locales, :set_translations_table_name, :to => :translation_class
|
5
5
|
|
6
|
+
if ::ActiveRecord::VERSION::STRING < "5.0.0"
|
7
|
+
def columns_hash
|
8
|
+
super.except(*translated_attribute_names.map(&:to_s))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
6
12
|
def with_locales(*locales)
|
7
13
|
all.merge translation_class.with_locales(*locales)
|
8
14
|
end
|
9
15
|
|
10
16
|
def with_translations(*locales)
|
11
17
|
locales = translated_locales if locales.empty?
|
12
|
-
preload(:translations).joins(:translations).readonly(false).with_locales(locales)
|
18
|
+
preload(:translations).joins(:translations).readonly(false).with_locales(locales).tap do |query|
|
19
|
+
query.distinct! unless locales.flatten.one?
|
20
|
+
end
|
13
21
|
end
|
14
22
|
|
15
23
|
def with_required_attributes
|
@@ -42,12 +50,13 @@ module Globalize
|
|
42
50
|
|
43
51
|
def translation_class
|
44
52
|
@translation_class ||= begin
|
45
|
-
|
46
|
-
|
53
|
+
if self.const_defined?(:Translation, false)
|
54
|
+
klass = self.const_get(:Translation, false)
|
55
|
+
else
|
47
56
|
klass = self.const_set(:Translation, Class.new(Globalize::ActiveRecord::Translation))
|
48
57
|
end
|
49
58
|
|
50
|
-
klass.belongs_to :globalized_model, :class_name => self.name, :foreign_key => translation_options[:foreign_key]
|
59
|
+
klass.belongs_to :globalized_model, :class_name => self.name, :foreign_key => translation_options[:foreign_key], inverse_of: :translations
|
51
60
|
klass
|
52
61
|
end
|
53
62
|
end
|
@@ -100,8 +109,8 @@ module Globalize
|
|
100
109
|
|
101
110
|
def define_translations_writer(name)
|
102
111
|
define_method(:"#{name}_translations=") do |value|
|
103
|
-
value.each do |(locale,
|
104
|
-
write_attribute name,
|
112
|
+
value.each do |(locale, _value)|
|
113
|
+
write_attribute name, _value, :locale => locale
|
105
114
|
end
|
106
115
|
end
|
107
116
|
end
|
@@ -8,12 +8,6 @@ module Globalize
|
|
8
8
|
super("Missing translated field #{field.inspect}")
|
9
9
|
end
|
10
10
|
end
|
11
|
-
|
12
|
-
class BadFieldType < MigrationError
|
13
|
-
def initialize(name, type)
|
14
|
-
super("Bad field type for field #{name.inspect} (#{type.inspect}), should be :string or :text")
|
15
|
-
end
|
16
|
-
end
|
17
11
|
end
|
18
12
|
end
|
19
|
-
end
|
13
|
+
end
|
@@ -11,48 +11,46 @@ module Globalize
|
|
11
11
|
super.merge(translated_attributes)
|
12
12
|
end
|
13
13
|
|
14
|
-
def attributes=(
|
15
|
-
|
14
|
+
def attributes=(new_attributes, *options)
|
15
|
+
super unless new_attributes.respond_to?(:stringify_keys) && new_attributes.present?
|
16
|
+
attributes = new_attributes.stringify_keys
|
17
|
+
with_given_locale(attributes) { super(attributes.except("locale"), *options) }
|
16
18
|
end
|
17
19
|
|
18
|
-
def assign_attributes(
|
19
|
-
|
20
|
+
def assign_attributes(new_attributes, *options)
|
21
|
+
super unless new_attributes.respond_to?(:stringify_keys) && new_attributes.present?
|
22
|
+
attributes = new_attributes.stringify_keys
|
23
|
+
with_given_locale(attributes) { super(attributes.except("locale"), *options) }
|
20
24
|
end
|
21
25
|
|
22
|
-
def write_attribute(name, value,
|
23
|
-
return super(name, value) unless translated?(name)
|
26
|
+
def write_attribute(name, value, *args, &block)
|
27
|
+
return super(name, value, *args, &block) unless translated?(name)
|
24
28
|
|
25
|
-
options = {:locale => Globalize.locale}.merge(
|
29
|
+
options = {:locale => Globalize.locale}.merge(args.first || {})
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@changed_attributes.delete(name_str) if value == old
|
31
|
+
globalize.write(options[:locale], name, value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](attr_name)
|
35
|
+
if translated?(attr_name)
|
36
|
+
read_attribute(attr_name)
|
34
37
|
else
|
35
|
-
|
36
|
-
old = globalize.fetch(options[:locale], name)
|
37
|
-
old = old.dup if old.duplicable?
|
38
|
-
@changed_attributes[name_str] = old if value != old
|
38
|
+
read_attribute(attr_name) { |n| missing_attribute(n, caller) }
|
39
39
|
end
|
40
|
-
|
41
|
-
globalize.write(options[:locale], name, value)
|
42
40
|
end
|
43
41
|
|
44
|
-
def read_attribute(name, options = {})
|
42
|
+
def read_attribute(name, options = {}, &block)
|
45
43
|
options = {:translated => true, :locale => nil}.merge(options)
|
46
|
-
return super(name) unless options[:translated]
|
44
|
+
return super(name, &block) unless options[:translated]
|
47
45
|
|
48
46
|
if translated?(name)
|
49
|
-
if (value = globalize.fetch(options[:locale] || Globalize.locale, name))
|
47
|
+
if !(value = globalize.fetch(options[:locale] || Globalize.locale, name)).nil?
|
50
48
|
value
|
51
49
|
else
|
52
|
-
super(name)
|
50
|
+
super(name, &block)
|
53
51
|
end
|
54
52
|
else
|
55
|
-
super(name)
|
53
|
+
super(name, &block)
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|
@@ -83,8 +81,9 @@ module Globalize
|
|
83
81
|
|
84
82
|
options[locale].each do |key, value|
|
85
83
|
translation.send :"#{key}=", value
|
84
|
+
translation.globalized_model.send :"#{key}=", value
|
86
85
|
end
|
87
|
-
translation.save
|
86
|
+
translation.save if persisted?
|
88
87
|
end
|
89
88
|
globalize.reset
|
90
89
|
end
|
@@ -143,14 +142,17 @@ module Globalize
|
|
143
142
|
Globalize.fallbacks(locale)
|
144
143
|
end
|
145
144
|
|
146
|
-
def rollback
|
147
|
-
translation_caches[::Globalize.locale] = translation.previous_version
|
148
|
-
end
|
149
|
-
|
150
145
|
def save(*)
|
151
|
-
Globalize.with_locale(translation.locale || I18n.default_locale) do
|
152
|
-
|
146
|
+
result = Globalize.with_locale(translation.locale || I18n.default_locale) do
|
147
|
+
without_fallbacks do
|
148
|
+
super
|
149
|
+
end
|
153
150
|
end
|
151
|
+
if result
|
152
|
+
globalize.clear_dirty
|
153
|
+
end
|
154
|
+
|
155
|
+
result
|
154
156
|
end
|
155
157
|
|
156
158
|
def column_for_attribute name
|
@@ -163,6 +165,16 @@ module Globalize
|
|
163
165
|
[super, translation.cache_key].join("/")
|
164
166
|
end
|
165
167
|
|
168
|
+
def changed?
|
169
|
+
changed_attributes.present? || translations.any?(&:changed?)
|
170
|
+
end
|
171
|
+
|
172
|
+
# need to access instance variable directly since changed_attributes
|
173
|
+
# is frozen as of Rails 4.2
|
174
|
+
def original_changed_attributes
|
175
|
+
@changed_attributes
|
176
|
+
end
|
177
|
+
|
166
178
|
protected
|
167
179
|
|
168
180
|
def each_locale_and_translated_attribute
|
@@ -184,15 +196,23 @@ module Globalize
|
|
184
196
|
translation_caches.clear
|
185
197
|
end
|
186
198
|
|
187
|
-
def with_given_locale(
|
188
|
-
attributes
|
199
|
+
def with_given_locale(_attributes, &block)
|
200
|
+
attributes = _attributes.stringify_keys
|
189
201
|
|
190
|
-
if locale = attributes.try(:delete,
|
202
|
+
if locale = attributes.try(:delete, "locale")
|
191
203
|
Globalize.with_locale(locale, &block)
|
192
204
|
else
|
193
205
|
yield
|
194
206
|
end
|
195
207
|
end
|
208
|
+
|
209
|
+
def without_fallbacks
|
210
|
+
before = self.fallbacks_for_empty_translations
|
211
|
+
self.fallbacks_for_empty_translations = false
|
212
|
+
yield
|
213
|
+
ensure
|
214
|
+
self.fallbacks_for_empty_translations = before
|
215
|
+
end
|
196
216
|
end
|
197
217
|
end
|
198
218
|
end
|