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.
- checksums.yaml +7 -0
- data/Gemfile +15 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +27 -0
- data/lib/countrizable/active_record/act_macro.rb +116 -0
- data/lib/countrizable/active_record/adapter.rb +108 -0
- data/lib/countrizable/active_record/adapter_dirty.rb +56 -0
- data/lib/countrizable/active_record/attributes.rb +26 -0
- data/lib/countrizable/active_record/class_methods.rb +129 -0
- data/lib/countrizable/active_record/country_attributes_query.rb +181 -0
- data/lib/countrizable/active_record/country_value.rb +45 -0
- data/lib/countrizable/active_record/exceptions.rb +13 -0
- data/lib/countrizable/active_record/instance_methods.rb +246 -0
- data/lib/countrizable/active_record/migration.rb +215 -0
- data/lib/countrizable/active_record.rb +14 -0
- data/lib/countrizable/i18n/country_code.rb +9 -0
- data/lib/countrizable/i18n.rb +5 -0
- data/lib/countrizable/interpolation.rb +28 -0
- data/lib/countrizable/railtie.rb +4 -0
- data/lib/countrizable/version.rb +3 -0
- data/lib/countrizable.rb +95 -0
- data/lib/i18n/country_code.rb +7 -0
- data/lib/patches/active_record/persistence.rb +17 -0
- data/lib/patches/active_record/query_method.rb +3 -0
- data/lib/patches/active_record/rails4/query_method.rb +35 -0
- data/lib/patches/active_record/rails4/serialization.rb +22 -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/rails5_1/serialization.rb +22 -0
- data/lib/patches/active_record/rails5_1/uniqueness_validator.rb +45 -0
- data/lib/patches/active_record/relation.rb +12 -0
- data/lib/patches/active_record/serialization.rb +5 -0
- data/lib/patches/active_record/uniqueness_validator.rb +7 -0
- data/lib/patches/active_record/xml_attribute_serializer.rb +23 -0
- data/lib/tasks/countrizable_tasks.rake +4 -0
- 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
|