globalize 5.0.1 → 5.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|