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