countrizable 0.1.1

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 (37) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +15 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +28 -0
  5. data/Rakefile +27 -0
  6. data/lib/countrizable/active_record/act_macro.rb +116 -0
  7. data/lib/countrizable/active_record/adapter.rb +108 -0
  8. data/lib/countrizable/active_record/adapter_dirty.rb +56 -0
  9. data/lib/countrizable/active_record/attributes.rb +26 -0
  10. data/lib/countrizable/active_record/class_methods.rb +129 -0
  11. data/lib/countrizable/active_record/country_attributes_query.rb +181 -0
  12. data/lib/countrizable/active_record/country_value.rb +45 -0
  13. data/lib/countrizable/active_record/exceptions.rb +13 -0
  14. data/lib/countrizable/active_record/instance_methods.rb +246 -0
  15. data/lib/countrizable/active_record/migration.rb +215 -0
  16. data/lib/countrizable/active_record.rb +14 -0
  17. data/lib/countrizable/i18n/country_code.rb +9 -0
  18. data/lib/countrizable/i18n.rb +5 -0
  19. data/lib/countrizable/interpolation.rb +28 -0
  20. data/lib/countrizable/railtie.rb +4 -0
  21. data/lib/countrizable/version.rb +3 -0
  22. data/lib/countrizable.rb +95 -0
  23. data/lib/i18n/country_code.rb +7 -0
  24. data/lib/patches/active_record/persistence.rb +17 -0
  25. data/lib/patches/active_record/query_method.rb +3 -0
  26. data/lib/patches/active_record/rails4/query_method.rb +35 -0
  27. data/lib/patches/active_record/rails4/serialization.rb +22 -0
  28. data/lib/patches/active_record/rails4/uniqueness_validator.rb +42 -0
  29. data/lib/patches/active_record/rails5/uniqueness_validator.rb +47 -0
  30. data/lib/patches/active_record/rails5_1/serialization.rb +22 -0
  31. data/lib/patches/active_record/rails5_1/uniqueness_validator.rb +45 -0
  32. data/lib/patches/active_record/relation.rb +12 -0
  33. data/lib/patches/active_record/serialization.rb +5 -0
  34. data/lib/patches/active_record/uniqueness_validator.rb +7 -0
  35. data/lib/patches/active_record/xml_attribute_serializer.rb +23 -0
  36. data/lib/tasks/countrizable_tasks.rake +4 -0
  37. metadata +259 -0
@@ -0,0 +1,181 @@
1
+ module Countrizable
2
+ module ActiveRecord
3
+ module CountryAttributesQuery
4
+ class WhereChain < ::ActiveRecord::QueryMethods::WhereChain
5
+ def not(opts, *rest)
6
+ if parsed = @scope.clone.parse_country_conditions(opts)
7
+ @scope.join_country_values.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_country_conditions(opts)
18
+ join_country_values(super(parsed, *rest))
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def having(opts, *rest)
25
+ if parsed = parse_country_conditions(opts)
26
+ join_country_values(super(parsed, *rest))
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def order(opts, *rest)
33
+ if respond_to?(:country_attribute_names) && parsed = parse_countries_order(opts)
34
+ join_country_values super(parsed)
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def reorder(opts, *rest)
41
+ if respond_to?(:country_attribute_names) && parsed = parse_countries_order(opts)
42
+ join_country_values super(parsed)
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+ def group(*columns)
49
+ if respond_to?(:country_attribute_names) && parsed = parse_countries_columns(columns)
50
+ join_country_values super(parsed)
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def select(*columns)
57
+ if respond_to?(:country_attribute_names) && parsed = parse_countries_columns(columns)
58
+ join_country_values super(parsed)
59
+ else
60
+ super
61
+ end
62
+ end
63
+
64
+ def exists?(conditions = :none)
65
+ if parsed = parse_country_conditions(conditions)
66
+ with_country_values_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?(:country_attribute_names) && country_column?(column_name)
75
+ args[1] = country_column_name(column_name)
76
+ join_country_values.calculate(*args)
77
+ else
78
+ super
79
+ end
80
+ end
81
+
82
+ def pluck(*column_names)
83
+ if respond_to?(:country_values_attribute_names) && parsed = parse_countries_columns(column_names)
84
+ join_country_values.pluck(*parsed)
85
+ else
86
+ super
87
+ end
88
+ end
89
+
90
+ def with_country_values_in_fallbacks
91
+ with_country_values(Countrizable.fallbacks)
92
+ end
93
+
94
+ def parse_country_conditions(opts)
95
+ if opts.is_a?(Hash) && respond_to?(:country_attribute_names) && (keys = opts.symbolize_keys.keys & country_attribute_names).present?
96
+ opts = opts.dup
97
+ keys.each { |key| opts[country_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?(:country_values_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 == country_values_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_country_values(relation = self)
120
+ if relation.joins_values.include?(:country_values)
121
+ relation
122
+ else
123
+ relation.with_country_values_in_fallbacks
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def arel_countries_order_node(column, direction)
130
+ unless countries_column?(column)
131
+ return self.arel_table[column].send(direction)
132
+ end
133
+
134
+ full_column = countries_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
+ country_value_class.arel_table[column].send(direction)
145
+ end
146
+
147
+ def parse_countries_order(opts)
148
+ case opts
149
+ when Hash
150
+ # Do not process nothing unless there is at least a country column
151
+ # so that the `order` statement will be processed by the original
152
+ # ActiveRecord method
153
+ return nil unless opts.find { |col, dir| country_column?(col) }
154
+
155
+ # Build order arel nodes for countrys and non-countries statements
156
+ ordering = opts.map do |column, direction|
157
+ arel_country_order_node(column, direction)
158
+ end
159
+
160
+ order(ordering).order_values
161
+ when Symbol
162
+ parse_countries_order({ opts => :asc })
163
+ when Array
164
+ parse_countries_order(Hash[opts.collect { |opt| [opt, :asc] } ])
165
+ else # failsafe returns nothing
166
+ nil
167
+ end
168
+ end
169
+
170
+ def parse_countries_columns(columns)
171
+ if columns.is_a?(Array) && (columns.flatten & country_attribute_names).present?
172
+ columns.flatten.map { |column| country_column?(column) ? country_column_name(column) : column }
173
+ end
174
+ end
175
+
176
+ def country_column?(column)
177
+ country_attribute_names.include?(column)
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,45 @@
1
+ module Countrizable
2
+ module ActiveRecord
3
+ class CountryValue < ::ActiveRecord::Base
4
+
5
+ validates :country_code, :presence => true
6
+
7
+ class << self
8
+ # Sometimes ActiveRecord queries .table_exists? before the table name
9
+ # has even been set which results in catastrophic failure.
10
+ def table_exists?
11
+ table_name.present? && super
12
+ end
13
+
14
+ def with_country_codes(*country_codes)
15
+ # Avoid using "IN" with SQL queries when only using one locale.
16
+ country_codes = country_codes.flatten.map(&:to_s)
17
+ country_codes = country_codes.first if country_codes.one?
18
+ where :country_code => country_codes
19
+ end
20
+ alias with_country_codes with_country_codes
21
+
22
+ def valued_country_codes #prev translated_locales
23
+ select('DISTINCT country_code').order(:country_code).map(&:country_code)
24
+ end
25
+ end
26
+
27
+ def country_code
28
+ _country_code = read_attribute :country_code
29
+ _country_code.present? ? _country_code.to_sym : _country_code
30
+ end
31
+
32
+ def country_code=(country_code)
33
+ write_attribute :country_code, country_code.to_s
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ # Setting this will force polymorphic associations to subclassed objects
40
+ # to use their table_name rather than the parent object's table name,
41
+ # which will allow you to get their models back in a more appropriate
42
+ # format.
43
+ #
44
+ # See http://www.ruby-forum.com/topic/159894 for details.
45
+ Countrizable::ActiveRecord::CountryValue.abstract_class = true
@@ -0,0 +1,13 @@
1
+ module Countrizable
2
+ module ActiveRecord
3
+ module Exceptions
4
+ class MigrationError < StandardError; end
5
+
6
+ class BadFieldName < MigrationError
7
+ def initialize(field)
8
+ super("Missing country valued field #{field.inspect}")
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,246 @@
1
+ module Countrizable
2
+ module ActiveRecord
3
+ module InstanceMethods
4
+ delegate :values_country_codes, :to => :country_values
5
+
6
+ def countrizable
7
+ @countrizable ||= Adapter.new(self)
8
+ end
9
+
10
+ def attributes
11
+ super.merge(country_attributed_attributes)
12
+ end
13
+
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_country_code(attributes) { super(attributes.except("country_code"), *options) }
18
+ end
19
+
20
+ if Countrizable.rails_52?
21
+
22
+ # In Rails 5.2 we need to override *_assign_attributes* as it's called earlier
23
+ # in the stack (before *assign_attributes*)
24
+ # See https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_assignment.rb#L11
25
+ def _assign_attributes(new_attributes)
26
+ attributes = new_attributes.stringify_keys
27
+ with_given_country_code(attributes) { super(attributes.except("country_code")) }
28
+ end
29
+
30
+ else
31
+
32
+ def assign_attributes(new_attributes, *options)
33
+ super unless new_attributes.respond_to?(:stringify_keys) && new_attributes.present?
34
+ attributes = new_attributes.stringify_keys
35
+ with_given_country_code(attributes) { super(attributes.except("country_code"), *options) }
36
+ end
37
+
38
+ end
39
+
40
+ def write_attribute(name, value, *args, &block)
41
+ return super(name, value, *args, &block) unless country_attributed?(name)
42
+
43
+ options = {:country_code => Countrizable.country_code}.merge(args.first || {})
44
+
45
+ countrizable.write(options[:country_code], name, value)
46
+ end
47
+
48
+ def [](attr_name)
49
+ if country_attributed?(attr_name)
50
+ read_attribute(attr_name)
51
+ else
52
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
53
+ end
54
+ end
55
+
56
+ def read_attribute(attr_name, options = {}, &block)
57
+ name = if self.class.attribute_alias?(attr_name)
58
+ self.class.attribute_alias(attr_name).to_s
59
+ else
60
+ attr_name.to_s
61
+ end
62
+
63
+ name = self.class.primary_key if name == "id".freeze && self.class.primary_key
64
+
65
+ _read_attribute(name, options, &block)
66
+ end
67
+
68
+ def _read_attribute(attr_name, options = {}, &block)
69
+ country_value = read_country_attribute(attr_name, options, &block)
70
+ country_value.nil? ? super(attr_name, &block) : country_value
71
+ end
72
+
73
+ def attribute_names
74
+ country_attribute_names.map(&:to_s) + super
75
+ end
76
+
77
+ delegate :country_attributed?, :to => :class
78
+
79
+ def country_attributed_attributes
80
+ country_attribute_names.inject({}) do |attributes, name|
81
+ attributes.merge(name.to_s => send(name))
82
+ end
83
+ end
84
+
85
+ # This method is basically the method built into Rails
86
+ # but we have to pass {:country_attributed => false}
87
+ def uncountry_attributes
88
+ attribute_names.inject({}) do |attrs, name|
89
+ attrs[name] = read_attribute(name, {:country_attributed => false}); attrs
90
+ end
91
+ end
92
+
93
+ def set_country_values(options)
94
+ options.keys.each do |country_code|
95
+ country_value = country_value_for(country_code) ||
96
+ country_values.build(:country_code => country_code.to_s)
97
+
98
+ options[country_code].each do |key, value|
99
+ country_value.send :"#{key}=", value
100
+ country_value.countrizable_model.send :"#{key}=", value
101
+ end
102
+ country_value.save if persisted?
103
+ end
104
+ countrizable.reset
105
+ end
106
+
107
+ def reload(options = nil)
108
+ country_value_caches.clear
109
+ country_attribute_names.each { |name| @attributes.reset(name.to_s) }
110
+ countrizable.reset
111
+ super(options)
112
+ end
113
+
114
+ def initialize_dup(other)
115
+ @countrizable = nil
116
+ @country_value_caches = nil
117
+ super
118
+ other.each_country_code_and_country_attribute do |country_code, name|
119
+ countrizable.write(country_code, name, other.countrizable.fetch(country_code, name) )
120
+ end
121
+ end
122
+
123
+ def country_value
124
+ country_value_for(::Countrizable.country_code)
125
+ end
126
+
127
+ def country_value_for(country_code, build_if_missing = true)
128
+ unless country_value_caches[country_code]
129
+ # Fetch values from database as those in the country values collection may be incomplete
130
+ _country_value = country_values.detect{|t| t.country_code.to_s == country_code.to_s}
131
+ _country_value ||= country_values.with_country_code(country_code).first unless country_values.loaded?
132
+ _country_value ||= country_values.build(:country_code => country_code) if build_if_missing
133
+ country_value_caches[country_code] = _country_value if _country_value
134
+ end
135
+ country_value_caches[country_code]
136
+ end
137
+
138
+ def country_value_caches
139
+ @country_value_caches ||= {}
140
+ end
141
+
142
+ def country_values_by_country_code
143
+ country_values.each_with_object(HashWithIndifferentAccess.new) do |t, hash|
144
+ hash[t.country_code] = block_given? ? yield(t) : t
145
+ end
146
+ end
147
+
148
+ def country_attribute_by_country_code(name)
149
+ country_values_by_country_code(&:"#{name}")
150
+ end
151
+
152
+ # Get available country_codes from country_value association, without a separate distinct query
153
+ def available_country_codes
154
+ country_values.map(&:country_code).uniq
155
+ end
156
+
157
+ def countrizable_fallbacks(country_code)
158
+ Countrizable.fallbacks(country_code)
159
+ end
160
+
161
+ def save(*)
162
+ result = Countrizable.with_country_code(country_value.country_code || I18n.default_country_code) do
163
+ without_fallbacks do
164
+ super
165
+ end
166
+ end
167
+ if result
168
+ countrizable.clear_dirty
169
+ end
170
+
171
+ result
172
+ end
173
+
174
+ def column_for_attribute name
175
+ return super if country_attribute_names.exclude?(name)
176
+
177
+ countrizable.send(:column_for_attribute, name)
178
+ end
179
+
180
+ def cache_key
181
+ [super, country_value.cache_key].join("/")
182
+ end
183
+
184
+ def changed?
185
+ changed_attributes.present? || country_values.any?(&:changed?)
186
+ end
187
+
188
+ # need to access instance variable directly since changed_attributes
189
+ # is frozen as of Rails 4.2
190
+ def original_changed_attributes
191
+ @changed_attributes
192
+ end
193
+
194
+ protected
195
+
196
+ def each_country_code_and_country_attribute
197
+ used_country_codes.each do |country_code|
198
+ country_attribute_names.each do |name|
199
+ yield country_code, name
200
+ end
201
+ end
202
+ end
203
+
204
+ def used_country_codes
205
+ country_codes = countrizable.stash.keys.concat(countrizable.stash.keys).concat(country_values.valued_country_codes)
206
+ country_codes.uniq!
207
+ country_codes
208
+ end
209
+
210
+ def save_country_values!
211
+ countrizable.save_country_values!
212
+ country_value_caches.clear
213
+ end
214
+
215
+ def with_given_country_code(_attributes, &block)
216
+ attributes = _attributes.stringify_keys
217
+
218
+ if country_code = attributes.try(:delete, "country_code")
219
+ Countrizable.with_country_code(country_code, &block)
220
+ else
221
+ yield
222
+ end
223
+ end
224
+
225
+ def without_fallbacks
226
+ before = self.fallbacks_for_empty_country_values
227
+ self.fallbacks_for_empty_country_values = false
228
+ yield
229
+ ensure
230
+ self.fallbacks_for_empty_country_values = before
231
+ end
232
+
233
+ # nil or value
234
+ def read_country_attribute(name, options)
235
+ options = {:country_attributed => true, :country_code => nil}.merge(options) #:translated => true
236
+ return nil unless options[:country_attributed] #translated
237
+ return nil unless country_attributed?(name)
238
+
239
+ value = countrizable.fetch(options[:country_code] || Countrizable.country_code, name)
240
+ return nil if value.nil?
241
+
242
+ block_given? ? yield(value) : value
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,215 @@
1
+ require 'digest/sha1'
2
+
3
+ module Countrizable
4
+ module ActiveRecord
5
+ module Migration
6
+ def countrizable_migrator
7
+ @countrizable_migrator ||= Migrator.new(self)
8
+ end
9
+
10
+ delegate :create_country_values_table!, :add_country_value_fields!,
11
+ :drop_country_value_table!, :country_value_index_name,
12
+ :country_value_country_code_index_name, :to => :countrizable_migrator
13
+
14
+ class Migrator
15
+ include Countrizable::ActiveRecord::Exceptions
16
+
17
+ attr_reader :model
18
+ delegate :country_attribute_names, :connection, :table_name,
19
+ :table_name_prefix, :country_values_table_name, :columns, :to => :model
20
+
21
+ def initialize(model)
22
+ @model = model
23
+ end
24
+
25
+ def fields
26
+ @fields ||= complete_country_fields
27
+ end
28
+
29
+ def create_country_value_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
34
+ @fields = fields
35
+ # If we have fields we only want to create the country table with those fields
36
+ complete_country_fields if fields.blank?
37
+ validate_country_fields
38
+
39
+ create_country_value_table
40
+ add_country_value_fields!(fields, options)
41
+ create_country_values_index(options)
42
+ clear_schema_cache!
43
+ end
44
+
45
+ def add_country_value_fields!(fields, options = {})
46
+ @fields = fields
47
+ validate_country_fields
48
+ add_country_value_fields
49
+ clear_schema_cache!
50
+ move_data_to_country_table if options[:migrate_data]
51
+ remove_source_columns if options[:remove_source_columns]
52
+ clear_schema_cache!
53
+ end
54
+
55
+ def remove_source_columns
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
62
+ end
63
+
64
+ def drop_country_table!(options = {})
65
+ move_data_to_model_table if options[:migrate_data]
66
+ drop_country_values_index
67
+ drop_country_value_table
68
+ clear_schema_cache!
69
+ end
70
+
71
+ # This adds all the current country attributes of the model
72
+ # It's a problem because in early migrations would add all the country attributes
73
+ def complete_country_fields
74
+ country_attribute_names.each do |name|
75
+ @fields[name] ||= column_type(name)
76
+ end
77
+ end
78
+
79
+ def create_country_value_table
80
+ connection.create_table(country_values_table_name) do |t|
81
+ t.references table_name.sub(/^#{table_name_prefix}/, '').singularize, :null => false, :index => false, :type => column_type(model.primary_key).to_sym
82
+ t.string :country_code, :null => false
83
+ t.timestamps :null => false
84
+ end
85
+ end
86
+
87
+ def add_country_value_fields
88
+ connection.change_table(country_values_table_name) do |t|
89
+ fields.each do |name, options|
90
+ if options.is_a? Hash
91
+ t.column name, options.delete(:type), options
92
+ else
93
+ t.column name, options
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def create_country_values_index(options)
100
+ foreign_key = "#{table_name.sub(/^#{table_name_prefix}/, "").singularize}_id".to_sym
101
+ connection.add_index(
102
+ country_values_table_name,
103
+ foreign_key,
104
+ :name => country_value_index_name
105
+ )
106
+ # index for select('DISTINCT country_code') call in country_value.rb
107
+ connection.add_index(
108
+ country_values_table_name,
109
+ :country_code,
110
+ :name => country_value_country_code_index_name
111
+ )
112
+
113
+ if options[:unique_index]
114
+ connection.add_index(
115
+ country_values_table_name,
116
+ [foreign_key, :country_code],
117
+ :name => country_value_unique_index_name,
118
+ unique: true
119
+ )
120
+ end
121
+ end
122
+
123
+ def drop_country_value_table
124
+ connection.drop_table(country_values_table_name)
125
+ end
126
+
127
+ def drop_country_values_index
128
+ if connection.indexes(country_values_table_name).map(&:name).include?(country_value_index_name)
129
+ connection.remove_index(country_values_table_name, :name => country_value_index_name)
130
+ end
131
+ if connection.indexes(country_values_table_name).map(&:name).include?(country_value_country_code_index_name)
132
+ connection.remove_index(country_values_table_name, :name => country_value_country_code_index_name)
133
+ end
134
+ end
135
+
136
+ def move_data_to_country_value_table
137
+ model.find_each do |record|
138
+ country_value = record.country_value_for(I18n.country_code) || record.country_values.build(:country_code => I18n.country_code)
139
+ fields.each do |attribute_name, attribute_type|
140
+ country_value[attribute_name] = record.read_attribute(attribute_name, {:country_attributed => false})
141
+ end
142
+ country_value.save!
143
+ end
144
+ end
145
+
146
+ def move_data_to_model_table
147
+ add_missing_columns
148
+
149
+ # Find all of the country attributes for all records in the model.
150
+ all_country_attributes = model.all.collect{|m| m.attributes}
151
+ all_country_attributes.each do |country_record|
152
+ # Create a hash containing the country column names and their values.
153
+ country_attribute_names.inject(fields_to_update={}) do |f, name|
154
+ f.update({name.to_sym => country_record[name.to_s]})
155
+ end
156
+
157
+ # Now, update the actual model's record with the hash.
158
+ model.where(model.primary_key.to_sym => country_record[model.primary_key]).update_all(fields_to_update)
159
+ end
160
+ end
161
+
162
+ def validate_country_fields
163
+ fields.each do |name, options|
164
+ raise BadFieldName.new(name) unless valid_field_name?(name)
165
+ end
166
+ end
167
+
168
+ def column_type(name)
169
+ columns.detect { |c| c.name == name.to_s }.try(:type) || :string
170
+ end
171
+
172
+ def valid_field_name?(name)
173
+ country_attribute_names.include?(name)
174
+ end
175
+
176
+ def country_value_index_name
177
+ truncate_index_name "index_#{country_values_table_name}_on_#{table_name.singularize}_id"
178
+ end
179
+
180
+ def country_value_country_code_index_name
181
+ truncate_index_name "index_#{country_values_table_name}_on_country_code"
182
+ end
183
+
184
+ def country_value_unique_index_name
185
+ truncate_index_name "index_#{country_values_table_name}_on_#{table_name.singularize}_id_and_country_code"
186
+ end
187
+
188
+ def clear_schema_cache!
189
+ connection.schema_cache.clear! if connection.respond_to? :schema_cache
190
+ model::CountryValue.reset_column_information
191
+ model.reset_column_information
192
+ end
193
+
194
+ private
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
+
204
+ def add_missing_columns
205
+ clear_schema_cache!
206
+ country_attribute_names.map(&:to_s).each do |attribute|
207
+ unless model.column_names.include?(attribute)
208
+ connection.add_column(table_name, attribute, model::CountryValue.columns_hash[attribute].type)
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end