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,14 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module ActiveRecord
|
3
|
+
autoload :ActMacro, 'countrizable/active_record/act_macro'
|
4
|
+
autoload :Adapter, 'countrizable/active_record/adapter'
|
5
|
+
autoload :AdapterDirty, 'countrizable/active_record/adapter_dirty'
|
6
|
+
autoload :Attributes, 'countrizable/active_record/attributes'
|
7
|
+
autoload :ClassMethods, 'countrizable/active_record/class_methods'
|
8
|
+
autoload :Exceptions, 'countrizable/active_record/exceptions'
|
9
|
+
autoload :InstanceMethods, 'countrizable/active_record/instance_methods'
|
10
|
+
autoload :Migration, 'countrizable/active_record/migration'
|
11
|
+
autoload :CountryValue, 'countrizable/active_record/country_value'
|
12
|
+
autoload :CountryAttributesQuery, 'countrizable/active_record/country_attributes_query'
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module Interpolation
|
3
|
+
def interpolate(name, model, args)
|
4
|
+
country_value = model.read_attribute(name, {:country_code => country_code_from(args)})
|
5
|
+
try_interpolation country_value, interpolation_args_from(args)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def interpolation_args_from(args)
|
11
|
+
args.detect {|a| a.is_a? Hash }
|
12
|
+
end
|
13
|
+
|
14
|
+
def country_code_from(args)
|
15
|
+
args.detect {|a| !a.is_a? Hash }
|
16
|
+
end
|
17
|
+
|
18
|
+
def try_interpolation(country_value,args)
|
19
|
+
if args
|
20
|
+
I18n.interpolate(country_value,args)
|
21
|
+
else
|
22
|
+
country_value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
extend self
|
27
|
+
end
|
28
|
+
end
|
data/lib/countrizable.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require "countrizable/railtie"
|
2
|
+
require 'request_store'
|
3
|
+
require 'active_record'
|
4
|
+
require 'patches/active_record/xml_attribute_serializer'
|
5
|
+
require 'patches/active_record/query_method'
|
6
|
+
require 'patches/active_record/relation'
|
7
|
+
require 'patches/active_record/serialization'
|
8
|
+
require 'patches/active_record/uniqueness_validator'
|
9
|
+
require 'patches/active_record/persistence'
|
10
|
+
|
11
|
+
module Countrizable
|
12
|
+
autoload :ActiveRecord, 'countrizable/active_record'
|
13
|
+
autoload :Interpolation, 'countrizable/interpolation'
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def country_code
|
17
|
+
read_country_code || 'es'
|
18
|
+
end
|
19
|
+
|
20
|
+
def country_code=(country_code)
|
21
|
+
set_country_code(country_code)
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_country_code(country_code, &block)
|
25
|
+
previous_country_code = read_country_code
|
26
|
+
begin
|
27
|
+
set_country_code(country_code)
|
28
|
+
result = yield(country_code)
|
29
|
+
ensure
|
30
|
+
set_country_code(previous_country_code)
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_country_codes(*country_codes, &block)
|
36
|
+
country_codes.flatten.map do |country_code|
|
37
|
+
with_country_code(country_code, &block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def fallbacks=(country_codes)
|
42
|
+
set_fallbacks(country_codes)
|
43
|
+
end
|
44
|
+
|
45
|
+
def fallbacks(for_country_code = self.country_code)
|
46
|
+
read_fallbacks[for_country_code] || default_fallbacks(for_country_code)
|
47
|
+
end
|
48
|
+
|
49
|
+
def default_fallbacks(for_country_code = self.country_code)
|
50
|
+
[for_country_code.to_sym]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Thread-safe global storage
|
54
|
+
def storage
|
55
|
+
RequestStore.store
|
56
|
+
end
|
57
|
+
|
58
|
+
def rails_5?
|
59
|
+
::ActiveRecord.version >= Gem::Version.new('5.1.0')
|
60
|
+
end
|
61
|
+
|
62
|
+
def rails_52?
|
63
|
+
::ActiveRecord.version >= Gem::Version.new('5.2.0')
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def read_country_code
|
69
|
+
storage[:countrizable_country_code]
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_country_code(country_code)
|
73
|
+
storage[:countrizable_country_code] = country_code.try(:to_sym)
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_fallbacks
|
77
|
+
storage[:countrizable_fallbacks] || HashWithIndifferentAccess.new
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_fallbacks(country_codes)
|
81
|
+
fallback_hash = HashWithIndifferentAccess.new
|
82
|
+
|
83
|
+
country_codes.each do |key, value|
|
84
|
+
fallback_hash[key] = value.presence || [key]
|
85
|
+
end if country_codes.present?
|
86
|
+
|
87
|
+
storage[:countrizable_country_code] = fallback_hash
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
ActiveRecord::Base.class_attribute :countrizable_serialized_attributes, instance_writer: false
|
93
|
+
ActiveRecord::Base.countrizable_serialized_attributes = {}
|
94
|
+
|
95
|
+
ActiveRecord::Base.extend(Countrizable::ActiveRecord::ActMacro)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module Persistence
|
3
|
+
# Updates the associated record with values matching those of the instance attributes.
|
4
|
+
# Returns the number of affected rows.
|
5
|
+
def _update_record(attribute_names = self.attribute_names)
|
6
|
+
attribute_names_without_country_attributed = attribute_names.select{ |k| not respond_to?('country_attributed?') or not country_attributed?(k) }
|
7
|
+
super(attribute_names_without_country_attributed)
|
8
|
+
end
|
9
|
+
|
10
|
+
def _create_record(attribute_names = self.attribute_names)
|
11
|
+
attribute_names_without_country_attributed = attribute_names.select{ |k| not respond_to?('country_attributed?') or not country_attributed?(k) }
|
12
|
+
super(attribute_names_without_country_attributed)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
ActiveRecord::Persistence.send(:prepend, Countrizable::Persistence)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_record/attribute_methods/query'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module AttributeMethods
|
5
|
+
module Query
|
6
|
+
def query_attribute(attr_name)
|
7
|
+
unless value = read_attribute(attr_name)
|
8
|
+
false
|
9
|
+
else
|
10
|
+
column = self.class.columns_hash[attr_name]
|
11
|
+
if column.nil?
|
12
|
+
|
13
|
+
# TODO submit a rails patch
|
14
|
+
|
15
|
+
# not sure what active_record tests say but i guess this should mean:
|
16
|
+
# call to_i and check zero? if the value is a Numeric or starts with
|
17
|
+
# a digit, so it can meaningfully be typecasted by to_i
|
18
|
+
|
19
|
+
# if Numeric === value || value !~ /[^0-9]/
|
20
|
+
if Numeric === value || value.to_s =~ /^[0-9]/
|
21
|
+
!value.to_i.zero?
|
22
|
+
else
|
23
|
+
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
|
24
|
+
!value.blank?
|
25
|
+
end
|
26
|
+
elsif column.number?
|
27
|
+
!value.zero?
|
28
|
+
else
|
29
|
+
!value.blank?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module AttributeMethods
|
3
|
+
module Serialization
|
4
|
+
def serialize(attr_name, class_name_or_coder = Object)
|
5
|
+
super(attr_name, class_name_or_coder)
|
6
|
+
|
7
|
+
coder = if class_name_or_coder == ::JSON
|
8
|
+
::ActiveRecord::Coders::JSON
|
9
|
+
elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
|
10
|
+
class_name_or_coder
|
11
|
+
else
|
12
|
+
::ActiveRecord::Coders::YAMLColumn.new(class_name_or_coder)
|
13
|
+
end
|
14
|
+
|
15
|
+
self.countrizable_serialized_attributes = countrizable_serialized_attributes.dup
|
16
|
+
self.countrizable_serialized_attributes[attr_name] = coder
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveRecord::AttributeMethods::Serialization::ClassMethods.send(:prepend, Countrizable::AttributeMethods::Serialization)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'active_record/validations/uniqueness.rb'
|
2
|
+
|
3
|
+
module Countrizable
|
4
|
+
module UniquenessValidatorOverride
|
5
|
+
def validate_each(record, attribute, value)
|
6
|
+
klass = record.class
|
7
|
+
if klass.country_attributes? && klass.country_attributed?(attribute)
|
8
|
+
finder_class = klass.country_value_class
|
9
|
+
table = finder_class.arel_table
|
10
|
+
|
11
|
+
relation = build_relation(finder_class, table, attribute, value).and(table[:locale].eq(Countrizable.locale))
|
12
|
+
relation = relation.and(table[klass.reflect_on_association(:country_values).foreign_key].not_eq(record.send(:id))) if record.persisted?
|
13
|
+
|
14
|
+
country_scopes = Array(options[:scope]) & klass.country_attribute_names
|
15
|
+
uncountry_scopes = Array(options[:scope]) - country_scopes
|
16
|
+
|
17
|
+
uncountry_scopes.each do |scope_item|
|
18
|
+
scope_value = record.send(scope_item)
|
19
|
+
reflection = klass.reflect_on_association(scope_item)
|
20
|
+
if reflection
|
21
|
+
scope_value = record.send(reflection.foreign_key)
|
22
|
+
scope_item = reflection.foreign_key
|
23
|
+
end
|
24
|
+
relation = relation.and(find_finder_class_for(record).arel_table[scope_item].eq(scope_value))
|
25
|
+
end
|
26
|
+
|
27
|
+
country_scopes.each do |scope_item|
|
28
|
+
scope_value = record.send(scope_item)
|
29
|
+
relation = relation.and(table[scope_item].eq(scope_value))
|
30
|
+
end
|
31
|
+
|
32
|
+
if klass.unscoped.with_country_values.where(relation).exists?
|
33
|
+
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
|
34
|
+
end
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
ActiveRecord::Validations::UniquenessValidator.send :prepend, Countrizable::UniquenessValidatorOverride
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module Validations
|
3
|
+
module UniquenessValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
klass = record.class
|
6
|
+
if klass.country_attributes? && klass.country_attributed?(attribute)
|
7
|
+
finder_class = klass.country_value_class
|
8
|
+
finder_table = finder_class.arel_table
|
9
|
+
relation = build_relation(finder_class, finder_table, attribute, value).where(country_code: Countrizable.country_code)
|
10
|
+
relation = relation.where.not(klass.reflect_on_association(:country_values).foreign_key => record.send(:id)) if record.persisted?
|
11
|
+
|
12
|
+
|
13
|
+
country_scopes = Array(options[:scope]) & klass.country_attribute_names
|
14
|
+
uncountry_scopes = Array(options[:scope]) - country_scopes
|
15
|
+
|
16
|
+
relation = relation.joins(:countrizable_model) if uncountry_scopes.present?
|
17
|
+
uncountry_scopes.each do |scope_item|
|
18
|
+
scope_value = record.send(scope_item)
|
19
|
+
reflection = klass.reflect_on_association(scope_item)
|
20
|
+
if reflection
|
21
|
+
scope_value = record.send(reflection.foreign_key)
|
22
|
+
scope_item = reflection.foreign_key
|
23
|
+
end
|
24
|
+
relation = relation.where(find_finder_class_for(record).table_name => { scope_item => scope_value })
|
25
|
+
end
|
26
|
+
|
27
|
+
country_scopes.each do |scope_item|
|
28
|
+
scope_value = record.send(scope_item)
|
29
|
+
relation = relation.where(scope_item => scope_value)
|
30
|
+
end
|
31
|
+
relation = relation.merge(options[:conditions]) if options[:conditions]
|
32
|
+
|
33
|
+
# if klass.unscoped.with_country_values.where(relation).exists?
|
34
|
+
if relation.exists?
|
35
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
36
|
+
error_options[:value] = value
|
37
|
+
record.errors.add(attribute, :taken, error_options)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
super(record, attribute, value)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
ActiveRecord::Validations::UniquenessValidator.prepend Countrizable::Validations::UniquenessValidator
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module AttributeMethods
|
3
|
+
module Serialization
|
4
|
+
def serialize(attr_name, class_name_or_coder = Object)
|
5
|
+
super(attr_name, class_name_or_coder)
|
6
|
+
|
7
|
+
coder = if class_name_or_coder == ::JSON
|
8
|
+
::ActiveRecord::Coders::JSON
|
9
|
+
elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
|
10
|
+
class_name_or_coder
|
11
|
+
else
|
12
|
+
::ActiveRecord::Coders::YAMLColumn.new(attr_name, class_name_or_coder)
|
13
|
+
end
|
14
|
+
|
15
|
+
self.countrizable_serialized_attributes = countrizable_serialized_attributes.dup
|
16
|
+
self.countrizable_serialized_attributes[attr_name] = coder
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveRecord::AttributeMethods::Serialization::ClassMethods.send(:prepend, Countrizable::AttributeMethods::Serialization)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module Validations
|
3
|
+
module UniquenessValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
klass = record.class
|
6
|
+
if klass.country_attributes? && klass.country_attributed?(attribute)
|
7
|
+
finder_class = klass.country_value_class
|
8
|
+
relation = build_relation(finder_class, attribute, value).where(country_code: Countrizable.country_code)
|
9
|
+
relation = relation.where.not(klass.reflect_on_association(:country_values).foreign_key => record.send(:id)) if record.persisted?
|
10
|
+
|
11
|
+
|
12
|
+
country_scopes = Array(options[:scope]) & klass.country_attribute_names
|
13
|
+
uncountry_scopes = Array(options[:scope]) - country_scopes
|
14
|
+
|
15
|
+
relation = relation.joins(:countrizable_model) if uncountry_scopes.present?
|
16
|
+
uncountry_scopes.each do |scope_item|
|
17
|
+
scope_value = record.send(scope_item)
|
18
|
+
reflection = klass.reflect_on_association(scope_item)
|
19
|
+
if reflection
|
20
|
+
scope_value = record.send(reflection.foreign_key)
|
21
|
+
scope_item = reflection.foreign_key
|
22
|
+
end
|
23
|
+
relation = relation.where(find_finder_class_for(record).table_name => { scope_item => scope_value })
|
24
|
+
end
|
25
|
+
|
26
|
+
country_scopes.each do |scope_item|
|
27
|
+
scope_value = record.send(scope_item)
|
28
|
+
relation = relation.where(scope_item => scope_value)
|
29
|
+
end
|
30
|
+
relation = relation.merge(options[:conditions]) if options[:conditions]
|
31
|
+
|
32
|
+
if relation.exists?
|
33
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
34
|
+
error_options[:value] = value
|
35
|
+
record.errors.add(attribute, :taken, error_options)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
super(record, attribute, value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
ActiveRecord::Validations::UniquenessValidator.prepend Countrizable::Validations::UniquenessValidator
|
@@ -0,0 +1,12 @@
|
|
1
|
+
if ::ActiveRecord::VERSION::STRING >= "5.0.0"
|
2
|
+
module Countrizable
|
3
|
+
module Relation
|
4
|
+
def where_values_hash(relation_table_name = table_name)
|
5
|
+
return super unless respond_to?(:country_values_table_name)
|
6
|
+
super.merge(super(country_values_table_name))
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecord::Relation.prepend Countrizable::Relation
|
12
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_record/serializers/xml_serializer'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
6
|
+
module Countrizable
|
7
|
+
module XmlSerializer
|
8
|
+
module Attribute
|
9
|
+
def compute_type
|
10
|
+
klass = @serializable.class
|
11
|
+
if klass.country_attributes? && klass.country_attribute_names.include?(name.to_sym)
|
12
|
+
:string
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if defined?(ActiveRecord::XmlSerializer)
|
22
|
+
ActiveRecord::XmlSerializer::Attribute.send(:prepend, Countrizable::XmlSerializer::Attribute)
|
23
|
+
end
|