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,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
|