countrizable 0.1.1

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