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.
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,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,9 @@
1
+ module Countrizable
2
+ module I18n
3
+ class << self
4
+ def country_code
5
+ "es"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module Countrizable
2
+ module I18n
3
+ autoload :CountryCode, 'countrizable/i18n/country_code'
4
+ end
5
+ 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
@@ -0,0 +1,4 @@
1
+ module Countrizable
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Countrizable
2
+ VERSION = '0.1.1'
3
+ end
@@ -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,7 @@
1
+ module I18n
2
+ class << self
3
+ def country_code
4
+ "es2"
5
+ end
6
+ end
7
+ end
@@ -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,3 @@
1
+ if ::ActiveRecord::VERSION::STRING < "5.0.0"
2
+ require_relative 'rails4/query_method'
3
+ end
@@ -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,5 @@
1
+ if ::ActiveRecord::VERSION::STRING < "5.1.0"
2
+ require_relative 'rails4/serialization'
3
+ else
4
+ require_relative 'rails5_1/serialization'
5
+ end
@@ -0,0 +1,7 @@
1
+ if ::ActiveRecord::VERSION::STRING < "5.0.0"
2
+ require_relative 'rails4/uniqueness_validator'
3
+ elsif ::ActiveRecord::VERSION::STRING < "5.1.0"
4
+ require_relative 'rails5/uniqueness_validator'
5
+ else
6
+ require_relative 'rails5_1/uniqueness_validator'
7
+ 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
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :countrizable do
3
+ # # Task goes here
4
+ # end