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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '039886e40e62d429443e1ade142f7ed8a6b5b3e83a12b7a7fca4fa109b5b3aff'
|
4
|
+
data.tar.gz: 51e4c3986a33eef57b917eceaa7fd6fb882ffcaabd66ff796fa2e33e72fd48bc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d76198119c24f64c9c77b27bd0c45d77c224001f2c443b8ffacc1e8913406fbb09ae72b4c115c28ba46e98fa04f320389b3f8b1b735fd8c14d90ed083fb7eba1
|
7
|
+
data.tar.gz: 0ffc9b2df18f3d71f878f2ddde6eaea2f4c0d65a33aeb67dd5965ca4a704b8bdba8866be1bf997f63eb864a035bd05d74b5d54c53893b841fb1424f3583af110
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
3
|
+
|
4
|
+
# Declare your gem's dependencies in countrizable.gemspec.
|
5
|
+
# Bundler will treat runtime dependencies like base dependencies, and
|
6
|
+
# development dependencies will be added by default to the :development group.
|
7
|
+
gemspec
|
8
|
+
|
9
|
+
# Declare any dependencies that are still in development here instead of in
|
10
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
11
|
+
# Git. Remember to move these dependencies to your gemspec before releasing
|
12
|
+
# your gem to rubygems.org.
|
13
|
+
|
14
|
+
# To use a debugger
|
15
|
+
# gem 'byebug', group: [:development, :test]
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 zeopix
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Countrizable
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'countrizable'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install countrizable
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
Contribution directions go here.
|
26
|
+
|
27
|
+
## License
|
28
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Countrizable'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'bundler/gem_tasks'
|
18
|
+
|
19
|
+
require 'rake/testtask'
|
20
|
+
|
21
|
+
Rake::TestTask.new(:test) do |t|
|
22
|
+
t.libs << 'test'
|
23
|
+
t.pattern = 'test/**/*_test.rb'
|
24
|
+
t.verbose = false
|
25
|
+
end
|
26
|
+
|
27
|
+
task default: :test
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module ActiveRecord
|
3
|
+
module ActMacro
|
4
|
+
def country_attribute(*attr_names)
|
5
|
+
options = attr_names.extract_options!
|
6
|
+
# Bypass setup_countries! if the initial bootstrapping is done already.
|
7
|
+
setup_countries!(options) unless country_attribute?
|
8
|
+
check_columns!(attr_names)
|
9
|
+
|
10
|
+
# Add any extra country attributes.
|
11
|
+
attr_names = attr_names.map(&:to_sym)
|
12
|
+
attr_names -= country_attribute_names if defined?(country_attribute_names)
|
13
|
+
|
14
|
+
allow_country_of_attributes(attr_names) if attr_names.present?
|
15
|
+
end
|
16
|
+
|
17
|
+
def class_name
|
18
|
+
@class_name ||= begin
|
19
|
+
class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].downcase.camelize
|
20
|
+
pluralize_table_names ? class_name.singularize : class_name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def country_attribute?
|
25
|
+
included_modules.include?(InstanceMethods)
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def allow_country_of_attributes(attr_names)
|
31
|
+
attr_names.each do |attr_name|
|
32
|
+
# Detect and apply serialization.
|
33
|
+
enable_serializable_attribute(attr_name)
|
34
|
+
|
35
|
+
# Create accessors for the attribute.
|
36
|
+
define_country_attr_accessor(attr_name)
|
37
|
+
define_country_values_accessor(attr_name)
|
38
|
+
|
39
|
+
# Add attribute to the list.
|
40
|
+
self.country_attribute_names << attr_name
|
41
|
+
end
|
42
|
+
|
43
|
+
begin
|
44
|
+
if ::ActiveRecord::VERSION::STRING > "5.0" && table_exists? && country_value_class.table_exists?
|
45
|
+
self.ignored_columns += country_attribute_names.map(&:to_s)
|
46
|
+
reset_column_information
|
47
|
+
end
|
48
|
+
rescue ::ActiveRecord::NoDatabaseError
|
49
|
+
warn 'Unable to connect to a database. Countrizable skipped ignoring columns of country attributes.'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_columns!(attr_names)
|
54
|
+
# If tables do not exist or Rails version is greater than 5, do not warn about conflicting columns
|
55
|
+
return unless ::ActiveRecord::VERSION::STRING < "5.0" && table_exists? && country_value_class.table_exists?
|
56
|
+
if (overlap = attr_names.map(&:to_s) & column_names).present?
|
57
|
+
ActiveSupport::Deprecation.warn(
|
58
|
+
["You have defined one or more country attributes with names that conflict with column(s) on the model table. ",
|
59
|
+
"Countrizable does not support this configuration anymore, remove or rename column(s) on the model table.\n",
|
60
|
+
"Model name (table name): #{model_name} (#{table_name})\n",
|
61
|
+
"Attribute name(s): #{overlap.join(', ')}\n"].join
|
62
|
+
)
|
63
|
+
end
|
64
|
+
rescue ::ActiveRecord::NoDatabaseError
|
65
|
+
warn 'Unable to connect to a database. Countrizable skipped checking attributes with conflicting column names.'
|
66
|
+
end
|
67
|
+
|
68
|
+
def apply_countrizable_options(options)
|
69
|
+
options[:table_name] ||= "#{table_name.singularize}_country_values"
|
70
|
+
options[:foreign_key] ||= class_name.foreign_key
|
71
|
+
|
72
|
+
class_attribute :country_attribute_names, :country_value_options, :fallbacks_for_empty_country_values
|
73
|
+
self.country_attribute_names = []
|
74
|
+
self.country_value_options = options
|
75
|
+
self.fallbacks_for_empty_country_values = options[:fallbacks_for_empty_country_values]
|
76
|
+
end
|
77
|
+
|
78
|
+
def enable_serializable_attribute(attr_name)
|
79
|
+
serializer = self.countrizable_serialized_attributes[attr_name]
|
80
|
+
if serializer.present?
|
81
|
+
if defined?(::ActiveRecord::Coders::YAMLColumn) &&
|
82
|
+
serializer.is_a?(::ActiveRecord::Coders::YAMLColumn)
|
83
|
+
serializer = serializer.object_class
|
84
|
+
end
|
85
|
+
|
86
|
+
country_value_class.send :serialize, attr_name, serializer
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def setup_countries!(options)
|
91
|
+
apply_countrizable_options(options)
|
92
|
+
|
93
|
+
include InstanceMethods
|
94
|
+
extend ClassMethods, Migration
|
95
|
+
|
96
|
+
country_value_class.table_name = options[:table_name]
|
97
|
+
|
98
|
+
has_many :country_values, :class_name => country_value_class.name,
|
99
|
+
:foreign_key => options[:foreign_key],
|
100
|
+
:dependent => :destroy,
|
101
|
+
:extend => HasManyExtensions,
|
102
|
+
:autosave => false,
|
103
|
+
:inverse_of => :countrizable_model
|
104
|
+
|
105
|
+
after_create :save_country_values!
|
106
|
+
after_update :save_country_values!
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
module HasManyExtensions
|
111
|
+
def find_or_initialize_by_country_code(country_code)
|
112
|
+
with_country_code(country_code.to_s).first || build(:country_code => country_code.to_s)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module ActiveRecord
|
3
|
+
class Adapter
|
4
|
+
# The cache caches attributes that already were looked up for read access.
|
5
|
+
# The stash keeps track of new or changed values that need to be saved.
|
6
|
+
attr_accessor :record, :stash
|
7
|
+
private :record=, :stash=
|
8
|
+
|
9
|
+
delegate :country_value_class, :to => :'record.class'
|
10
|
+
|
11
|
+
def initialize(record)
|
12
|
+
@record = record
|
13
|
+
@stash = Attributes.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def fetch_stash(country_code, name)
|
17
|
+
stash.read(country_code, name)
|
18
|
+
end
|
19
|
+
|
20
|
+
delegate :contains?, :to => :stash, :prefix => :stash
|
21
|
+
delegate :write, :to => :stash
|
22
|
+
|
23
|
+
def fetch(country_code, name)
|
24
|
+
record.countrizable_fallbacks(country_code).each do |fallback|
|
25
|
+
value = stash.contains?(fallback, name) ? fetch_stash(fallback, name) : fetch_attribute(fallback, name)
|
26
|
+
|
27
|
+
unless fallbacks_for?(value)
|
28
|
+
set_metadata(value, :country_code => fallback, :requested_country_code => country_code)
|
29
|
+
return value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def save_country_values!
|
37
|
+
stash.each do |country_code, attrs|
|
38
|
+
next if attrs.empty?
|
39
|
+
|
40
|
+
country_value = record.country_values_by_country_code[country_code] ||
|
41
|
+
record.country_values.build(country_code: country_code.to_s)
|
42
|
+
attrs.each do |name, value|
|
43
|
+
value = value.val if value.is_a?(Arel::Nodes::Casted)
|
44
|
+
country_value[name] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
ensure_foreign_key_for(country_value)
|
48
|
+
country_value.save!
|
49
|
+
end
|
50
|
+
|
51
|
+
reset
|
52
|
+
end
|
53
|
+
|
54
|
+
def reset
|
55
|
+
stash.clear
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
# Sometimes the country_values is initialised before a foreign key can be set.
|
61
|
+
def ensure_foreign_key_for(country_value)
|
62
|
+
# AR >= 4.1 reflections renamed to _reflections
|
63
|
+
country_value[country_value.class.reflections.stringify_keys["countrizable_model"].foreign_key] = record.id
|
64
|
+
end
|
65
|
+
|
66
|
+
def type_cast(name, value)
|
67
|
+
return value.presence unless column = column_for_attribute(name)
|
68
|
+
|
69
|
+
column.type_cast value
|
70
|
+
end
|
71
|
+
|
72
|
+
def column_for_attribute(name)
|
73
|
+
country_value_class.columns_hash[name.to_s]
|
74
|
+
end
|
75
|
+
|
76
|
+
def unserializable_attribute?(name, column)
|
77
|
+
column.text? && country_value_class.serialized_attributes[name.to_s]
|
78
|
+
end
|
79
|
+
|
80
|
+
def fetch_attribute(country_code, name)
|
81
|
+
country_value = record.country_value_for(country_code, false)
|
82
|
+
if country_value
|
83
|
+
country_value.send(name)
|
84
|
+
else
|
85
|
+
record.class.country_value_class.new.send(name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def set_metadata(object, metadata)
|
90
|
+
object.country_value_metadata.merge!(metadata) if object.respond_to?(:country_value_metadata)
|
91
|
+
object
|
92
|
+
end
|
93
|
+
|
94
|
+
def country_value_metadata_accessor(object)
|
95
|
+
return if obj.respond_to?(:country_value_metadata)
|
96
|
+
class << object; attr_accessor :country_value_metadata end
|
97
|
+
object.country_value_metadata ||= {}
|
98
|
+
end
|
99
|
+
|
100
|
+
def fallbacks_for?(object)
|
101
|
+
object.nil? || (fallbacks_for_empty_country_values? && object.blank?)
|
102
|
+
end
|
103
|
+
|
104
|
+
delegate :fallbacks_for_empty_country_values?, :to => :record, :prefix => false
|
105
|
+
prepend AdapterDirty
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module ActiveRecord
|
3
|
+
module AdapterDirty
|
4
|
+
def write country_code, name, value
|
5
|
+
# Dirty tracking, paraphrased from
|
6
|
+
# ActiveRecord::AttributeMethods::Dirty#write_attribute.
|
7
|
+
name = name.to_s
|
8
|
+
store_old_value name, country_code
|
9
|
+
old_values = dirty[name]
|
10
|
+
old_value = old_values[country_code]
|
11
|
+
is_changed = record.send :attribute_changed?, name
|
12
|
+
if is_changed && value == old_value
|
13
|
+
# If there's already a change, delete it if this undoes the change.
|
14
|
+
old_values.delete country_code
|
15
|
+
if old_values.empty?
|
16
|
+
_reset_attribute name
|
17
|
+
end
|
18
|
+
elsif !is_changed
|
19
|
+
# If there's not a change yet, record it.
|
20
|
+
record.send(:attribute_will_change!, name) if old_value != value
|
21
|
+
end
|
22
|
+
|
23
|
+
super country_code, name, value
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_writer :dirty
|
27
|
+
def dirty
|
28
|
+
@dirty ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def store_old_value name, country_code
|
32
|
+
dirty[name] ||= {}
|
33
|
+
unless dirty[name].key? country_code
|
34
|
+
old = fetch(country_code, name)
|
35
|
+
old = old.dup if old.duplicable?
|
36
|
+
dirty[name][country_code] = old
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def clear_dirty
|
41
|
+
self.dirty = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def _reset_attribute name
|
45
|
+
record.send("#{name}=", record.changed_attributes[name])
|
46
|
+
record.send(:clear_attribute_changes, [name])
|
47
|
+
end
|
48
|
+
|
49
|
+
def reset
|
50
|
+
clear_dirty
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Helper class for storing values per country_codes. Used by Countrizable::Adapter
|
2
|
+
# to stash and cache attribute values.
|
3
|
+
|
4
|
+
module Countrizable
|
5
|
+
module ActiveRecord
|
6
|
+
class Attributes < Hash # TODO: Think about using HashWithIndifferentAccess ?
|
7
|
+
def [](country_code)
|
8
|
+
country_code = country_code.to_sym
|
9
|
+
self[country_code] = {} unless has_key?(country_code)
|
10
|
+
self.fetch(country_code)
|
11
|
+
end
|
12
|
+
|
13
|
+
def contains?(country_code, name)
|
14
|
+
self[country_code].has_key?(name.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
def read(country_code, name)
|
18
|
+
self[country_code][name.to_s]
|
19
|
+
end
|
20
|
+
|
21
|
+
def write(country_code, name, value)
|
22
|
+
self[country_code][name.to_s] = value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Countrizable
|
2
|
+
module ActiveRecord
|
3
|
+
module ClassMethods
|
4
|
+
delegate :values_country_codes, :set_country_values_table_name, :to => :country_value_class
|
5
|
+
|
6
|
+
if ::ActiveRecord::VERSION::STRING < "5.0.0"
|
7
|
+
def columns_hash
|
8
|
+
super.except(*country_attribute_names.map(&:to_s))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_country_codes(*country_code)
|
13
|
+
all.merge country_value_class.with_country_codes(*country_codes)
|
14
|
+
end
|
15
|
+
|
16
|
+
def with_country_values(*country_codes)
|
17
|
+
country_codes = values_country_codes if country_codes.empty?
|
18
|
+
preload(:country_values).joins(:country_values).readonly(false).with_country_codes(country_codes).tap do |query|
|
19
|
+
query.distinct! unless country_codes.flatten.one?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_required_attributes
|
24
|
+
warn 'with_required_attributes is deprecated and will be removed in the next release of Countrizable.'
|
25
|
+
required_country_attributes.inject(all) do |scope, name|
|
26
|
+
scope.where("#{country_column_name(name)} IS NOT NULL")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def with_country_attribute(name, value, country = Countrizable.fallbacks)
|
31
|
+
with_country_values.where(
|
32
|
+
country_column_name(name) => value,
|
33
|
+
country_column_name(:country_code) => Array(country_codes).map(&:to_s)
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def country_attributed?(name)
|
38
|
+
country_attribute_names.include?(name.to_sym)
|
39
|
+
end
|
40
|
+
|
41
|
+
def required_attributes
|
42
|
+
warn 'required_attributes is deprecated and will be removed in the next release of Countrizable.'
|
43
|
+
validators.map { |v| v.attributes if v.is_a?(ActiveModel::Validations::PresenceValidator) }.flatten
|
44
|
+
end
|
45
|
+
|
46
|
+
def required_country_attributes
|
47
|
+
warn 'required_country_attributes is deprecated and will be removed in the next release of Countrizable.'
|
48
|
+
country_attribute_names & required_attributes
|
49
|
+
end
|
50
|
+
|
51
|
+
def country_value_class
|
52
|
+
@country_value_class ||= begin
|
53
|
+
if self.const_defined?(:CountryValue, false)
|
54
|
+
klass = self.const_get(:CountryValue, false)
|
55
|
+
else
|
56
|
+
klass = self.const_set(:CountryValue, Class.new(Countrizable::ActiveRecord::CountryValue))
|
57
|
+
end
|
58
|
+
|
59
|
+
klass.belongs_to :countrizable_model,
|
60
|
+
class_name: self.name,
|
61
|
+
foreign_key: country_value_options[:foreign_key],
|
62
|
+
inverse_of: :country_values,
|
63
|
+
touch: country_value_options.fetch(:touch, false)
|
64
|
+
klass
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def country_values_table_name
|
69
|
+
country_value_class.table_name
|
70
|
+
end
|
71
|
+
|
72
|
+
def country_column_name(name)
|
73
|
+
"#{country_value_class.table_name}.#{name}"
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Override the default relation method in order to return a subclass
|
79
|
+
# of ActiveRecord::Relation with custom finder and calculation methods
|
80
|
+
# for country attributes.
|
81
|
+
def relation
|
82
|
+
super.extending!(CountryAttributesQuery)
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def define_country_attr_reader(name)
|
88
|
+
define_method(name) do |*args|
|
89
|
+
Countrizable::Interpolation.interpolate(name, self, args)
|
90
|
+
end
|
91
|
+
alias_method :"#{name}_before_type_cast", name
|
92
|
+
end
|
93
|
+
|
94
|
+
def define_country_attr_writer(name)
|
95
|
+
define_method(:"#{name}=") do |value|
|
96
|
+
write_attribute(name, value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def define_country_attr_accessor(name)
|
101
|
+
attribute(name, ::ActiveRecord::Type::Value.new)
|
102
|
+
define_country_attr_reader(name)
|
103
|
+
define_country_attr_writer(name)
|
104
|
+
end
|
105
|
+
|
106
|
+
def define_country_values_reader(name)
|
107
|
+
define_method(:"#{name}_country_values") do
|
108
|
+
hash = country_attribute_by_country_code(name)
|
109
|
+
countrizable.stash.keys.each_with_object(hash) do |country_code, result|
|
110
|
+
result[country_code] = countrizable.fetch_stash(country_code, name) if countrizable.stash_contains?(country_code, name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def define_country_values_writer(name)
|
116
|
+
define_method(:"#{name}_country_values=") do |value|
|
117
|
+
value.each do |(country_code, _value)|
|
118
|
+
write_attribute name, _value, :country_code => country_code
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def define_country_values_accessor(name)
|
124
|
+
define_country_values_reader(name)
|
125
|
+
define_country_values_writer(name)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|