countrizable 0.1.1

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