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.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/Appraisals +33 -0
  3. data/CHANGELOG.md +12 -0
  4. data/CONTRIBUTING.md +15 -0
  5. data/Gemfile +0 -9
  6. data/Gemfile.lock +59 -241
  7. data/{readme.md → README.md} +124 -36
  8. data/Rakefile +33 -0
  9. data/lib/globalize/active_record/act_macro.rb +27 -1
  10. data/lib/globalize/active_record/adapter.rb +13 -5
  11. data/lib/globalize/active_record/adapter_dirty.rb +56 -0
  12. data/lib/globalize/active_record/class_methods.rb +23 -9
  13. data/lib/globalize/active_record/exceptions.rb +1 -7
  14. data/lib/globalize/active_record/instance_methods.rb +89 -41
  15. data/lib/globalize/active_record/migration.rb +58 -31
  16. data/lib/globalize/active_record/translated_attributes_query.rb +181 -0
  17. data/lib/globalize/active_record.rb +10 -9
  18. data/lib/globalize/version.rb +1 -1
  19. data/lib/globalize.rb +20 -6
  20. data/lib/patches/active_record/persistence.rb +6 -15
  21. data/lib/patches/active_record/query_method.rb +2 -34
  22. data/lib/patches/active_record/rails4/query_method.rb +35 -0
  23. data/lib/patches/active_record/rails4/serialization.rb +22 -0
  24. data/lib/patches/active_record/rails4/uniqueness_validator.rb +42 -0
  25. data/lib/patches/active_record/rails5/uniqueness_validator.rb +47 -0
  26. data/lib/patches/active_record/rails5_1/serialization.rb +22 -0
  27. data/lib/patches/active_record/rails5_1/uniqueness_validator.rb +45 -0
  28. data/lib/patches/active_record/relation.rb +12 -0
  29. data/lib/patches/active_record/serialization.rb +5 -24
  30. data/lib/patches/active_record/uniqueness_validator.rb +7 -39
  31. data/lib/patches/active_record/xml_attribute_serializer.rb +19 -9
  32. metadata +85 -22
  33. data/globalize.gemspec +0 -29
  34. 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!, :drop_translation_table!,
13
- :translation_index_name, :translation_locale_index_name,
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, :fields
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
- connection.remove_columns(table_name, *fields.keys)
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
- "#{table_name.sub(/^#{table_name_prefix}/, "").singularize}_id",
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.remove_index(translations_table_name, :name => translation_index_name)
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.default_locale) || record.translations.build(:locale => I18n.default_locale)
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 = @model.all.collect{|m| m.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
- @model.where(:id => translated_record['id']).update_all(fields_to_update)
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
- index_name = "index_#{translations_table_name}_on_#{table_name.singularize}_id"
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
- index_name = "index_#{translations_table_name}_on_locale"
165
- index_name.size < connection.index_name_length ? index_name :
166
- "index_#{Digest::SHA1.hexdigest(index_name)}"[0, connection.index_name_length]
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, 'globalize/active_record/act_macro'
4
- autoload :Adapter, 'globalize/active_record/adapter'
5
- autoload :Attributes, 'globalize/active_record/attributes'
6
- autoload :ClassMethods, 'globalize/active_record/class_methods'
7
- autoload :Exceptions, 'globalize/active_record/exceptions'
8
- autoload :InstanceMethods, 'globalize/active_record/instance_methods'
9
- autoload :Migration, 'globalize/active_record/migration'
10
- autoload :Translation, 'globalize/active_record/translation'
11
- autoload :QueryMethods, 'globalize/active_record/query_methods'
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
@@ -1,3 +1,3 @@
1
1
  module Globalize
2
- Version = '5.0.1'
2
+ Version = '5.2.0'
3
3
  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,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
- @globalize_locale
72
+ storage[:globalize_locale]
59
73
  end
60
74
 
61
75
  def set_locale(locale)
62
- @globalize_locale = locale.try(:to_sym)
76
+ storage[:globalize_locale] = locale.try(:to_sym)
63
77
  end
64
78
 
65
79
  def read_fallbacks
66
- @fallbacks || HashWithIndifferentAccess.new
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
- @fallbacks = fallback_hash
90
+ storage[:globalize_fallbacks] = fallback_hash
77
91
  end
78
92
  end
79
93
  end
80
94
 
81
- ActiveRecord::Base.mattr_accessor :globalize_serialized_attributes, instance_writer: false
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 ActiveRecord
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
- attributes_values = arel_attributes_with_values_for_update(attribute_names_without_translated)
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
- attributes_values = arel_attributes_with_values_for_create(attribute_names_without_translated)
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
- 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
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