active_delegate 0.2.1 → 1.0.0
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 +5 -5
- data/Gemfile.lock +11 -10
- data/Rakefile +1 -1
- data/lib/active_delegate.rb +14 -6
- data/lib/active_delegate/association/methods.rb +41 -0
- data/lib/active_delegate/associations.rb +21 -62
- data/lib/active_delegate/attribute/accessor.rb +102 -0
- data/lib/active_delegate/attribute/localize.rb +33 -0
- data/lib/active_delegate/attribute/methods.rb +48 -0
- data/lib/active_delegate/attribute/object.rb +135 -0
- data/lib/active_delegate/attributes.rb +130 -229
- data/lib/active_delegate/delegator.rb +60 -0
- data/lib/active_delegate/version.rb +1 -1
- metadata +15 -12
- data/lib/active_delegate/dirty.rb +0 -19
- data/lib/active_delegate/localized.rb +0 -26
- data/lib/active_delegate/read_write.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7ef4539c2b70f925e9e83e293bd7fec0a560586526efa6c6b486f36b1675fa65
|
4
|
+
data.tar.gz: ff272b205a8757d99b95cc45642b0f7c10cbb4171e63b3dd9149ab82389b03bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 620de75b4495080c73c9059ff361d2550ad2b2c031c19f68745f53b375dc1df119e18c33ab5f22027ae08cd9bb0e7b919740e87f0955f084133e580ce64fc638
|
7
|
+
data.tar.gz: 1c4e5f6b8f3cc93212b027d47e3098c1bd96a5c9b34bca9d4f00204892f8ab8a72e7dae65e407dc4d186860a3ba88448b2c814b5308068949418df5047ce57a0
|
data/Gemfile.lock
CHANGED
@@ -1,28 +1,29 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
active_delegate (0.1.
|
4
|
+
active_delegate (0.1.2)
|
5
5
|
activerecord (~> 5.0)
|
6
6
|
i18n (~> 0.8)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activemodel (5.1.
|
12
|
-
activesupport (= 5.1.
|
13
|
-
activerecord (5.1.
|
14
|
-
activemodel (= 5.1.
|
15
|
-
activesupport (= 5.1.
|
11
|
+
activemodel (5.1.4)
|
12
|
+
activesupport (= 5.1.4)
|
13
|
+
activerecord (5.1.4)
|
14
|
+
activemodel (= 5.1.4)
|
15
|
+
activesupport (= 5.1.4)
|
16
16
|
arel (~> 8.0)
|
17
|
-
activesupport (5.1.
|
17
|
+
activesupport (5.1.4)
|
18
18
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
19
19
|
i18n (~> 0.7)
|
20
20
|
minitest (~> 5.1)
|
21
21
|
tzinfo (~> 1.1)
|
22
22
|
arel (8.0.0)
|
23
23
|
concurrent-ruby (1.0.5)
|
24
|
-
i18n (0.
|
25
|
-
|
24
|
+
i18n (0.9.0)
|
25
|
+
concurrent-ruby (~> 1.0)
|
26
|
+
minitest (5.10.3)
|
26
27
|
rake (10.5.0)
|
27
28
|
thread_safe (0.3.6)
|
28
29
|
tzinfo (1.2.3)
|
@@ -38,4 +39,4 @@ DEPENDENCIES
|
|
38
39
|
rake (~> 10.0)
|
39
40
|
|
40
41
|
BUNDLED WITH
|
41
|
-
1.
|
42
|
+
1.15.4
|
data/Rakefile
CHANGED
data/lib/active_delegate.rb
CHANGED
@@ -12,23 +12,31 @@ module ActiveDelegate
|
|
12
12
|
# Delegate associations
|
13
13
|
def delegate_associations(*args)
|
14
14
|
options = args.extract_options!
|
15
|
-
|
15
|
+
options = options.reverse_merge(only: args)
|
16
|
+
|
17
|
+
Associations.new(self, options).call
|
18
|
+
end
|
19
|
+
|
20
|
+
# Delegate association
|
21
|
+
def delegate_association(association, options = {})
|
22
|
+
options = options.merge(only: association)
|
23
|
+
Associations.new(self, options).call
|
16
24
|
end
|
17
25
|
|
18
26
|
# Delegate attributes
|
19
27
|
def delegate_attributes(*args)
|
20
28
|
options = args.extract_options!
|
21
|
-
options = options.
|
29
|
+
options = options.reverse_merge(only: args).except(:cast_type, :alias)
|
22
30
|
|
23
|
-
Attributes.new(self, options)
|
31
|
+
Attributes.new(self, options).call
|
24
32
|
end
|
25
33
|
|
26
34
|
# Delegate attribute
|
27
|
-
def delegate_attribute(attribute, cast_type, options={})
|
35
|
+
def delegate_attribute(attribute, cast_type, options = {})
|
28
36
|
options = options.except(:only, :except)
|
29
|
-
options = options.merge(only:
|
37
|
+
options = options.merge(only: attribute, cast_type: cast_type)
|
30
38
|
|
31
|
-
Attributes.new(self, options)
|
39
|
+
Attributes.new(self, options).call
|
32
40
|
end
|
33
41
|
end
|
34
42
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActiveDelegate
|
2
|
+
module Association
|
3
|
+
class Methods
|
4
|
+
attr_reader :association_name, :association_class
|
5
|
+
|
6
|
+
# Initialize association methods
|
7
|
+
def initialize(association_name, association_class)
|
8
|
+
@association_name = association_name
|
9
|
+
@association_class = association_class
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get delegatable methods
|
13
|
+
def delegatable
|
14
|
+
delegatable = suffixed + prefixed
|
15
|
+
delegatable & association_class.instance_methods
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# Get method prefixes
|
21
|
+
def prefixes
|
22
|
+
['build_']
|
23
|
+
end
|
24
|
+
|
25
|
+
# Get method suffixes
|
26
|
+
def suffixes
|
27
|
+
['', '=', '_attributes', '_attributes=']
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get prefixed methods
|
31
|
+
def prefixed
|
32
|
+
prefixes.map { |s| :"#{s}#{attribute}" }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get suffixed methods
|
36
|
+
def suffixed
|
37
|
+
suffixes.map { |s| :"#{attribute}#{s}" }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,70 +1,29 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Initialize assoctiations
|
4
|
-
def initialize(model, options)
|
5
|
-
@model = model
|
6
|
-
@options = default_options.merge(options)
|
1
|
+
require 'active_delegate/delegator'
|
2
|
+
require 'active_delegate/association/methods'
|
7
3
|
|
8
|
-
|
4
|
+
module ActiveDelegate
|
5
|
+
class Associations < Delegator
|
6
|
+
# Get all associations
|
7
|
+
def association_reflections
|
8
|
+
association_class.reflect_on_all_associations
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
{ except: [], only: [], allow_nil: false, to: [] }
|
16
|
-
end
|
17
|
-
|
18
|
-
# Get association reflection
|
19
|
-
def association_reflection
|
20
|
-
assoc_name = @options.fetch(:to)
|
21
|
-
reflection = @model.reflect_on_association(assoc_name)
|
22
|
-
|
23
|
-
return reflection unless reflection.nil?
|
24
|
-
raise "#{@model.name} don't have the association #{assoc_name}"
|
25
|
-
end
|
26
|
-
|
27
|
-
# Get model association class
|
28
|
-
def association_class
|
29
|
-
association_reflection.klass
|
30
|
-
end
|
31
|
-
|
32
|
-
# Get all associations
|
33
|
-
def association_reflections
|
34
|
-
association_class.reflect_on_all_associations
|
35
|
-
end
|
36
|
-
|
37
|
-
# Get singular model association names
|
38
|
-
def association_names
|
39
|
-
association_reflections.map(&:name)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Get delegatable associations
|
43
|
-
def delegatable_associations
|
44
|
-
associations = association_names.map(&:to_sym)
|
45
|
-
associations = associations & @options[:only].to_a if @options[:only].present?
|
46
|
-
associations = associations - @options[:except].to_a if @options[:except].present?
|
47
|
-
|
48
|
-
associations.map(&:to_sym)
|
49
|
-
end
|
50
|
-
|
51
|
-
# Check if association is collection
|
52
|
-
def collection_association?(association)
|
53
|
-
collections = association_reflections.select(&:collection?).map(&:name)
|
54
|
-
association.in? collections
|
55
|
-
end
|
56
|
-
|
57
|
-
# Delegate associations
|
58
|
-
def delegate_associations
|
59
|
-
options = { to: @options[:to], allow_nil: @options[:allow_nil] }
|
11
|
+
# Get singular model association names
|
12
|
+
def association_names
|
13
|
+
association_reflections.map(&:name)
|
14
|
+
end
|
60
15
|
|
61
|
-
|
62
|
-
|
63
|
-
|
16
|
+
# Get delegatable associations
|
17
|
+
def delegatable_associations
|
18
|
+
delegation_args(association_names)
|
19
|
+
end
|
64
20
|
|
65
|
-
|
66
|
-
|
67
|
-
|
21
|
+
# Delegate associations
|
22
|
+
def call
|
23
|
+
delegatable_associations.each do |association|
|
24
|
+
methods = Association::Methods.new(association, association_class)
|
25
|
+
model.delegate(*methods.delegatable, delegation_options)
|
68
26
|
end
|
27
|
+
end
|
69
28
|
end
|
70
29
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module ActiveDelegate
|
2
|
+
module Attribute
|
3
|
+
class Accessor
|
4
|
+
attr_reader :record, :options
|
5
|
+
|
6
|
+
# Initialize attribute
|
7
|
+
def initialize(record, options = {})
|
8
|
+
@record = record
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get default value
|
13
|
+
def default_value
|
14
|
+
options[:default]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get association name
|
18
|
+
def association_name
|
19
|
+
options[:association].to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get association attribute name
|
23
|
+
def attribute_name
|
24
|
+
options[:attribute].to_sym
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get record attribute type
|
28
|
+
def read_type
|
29
|
+
options[:read_type]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get association attribute type
|
33
|
+
def write_type
|
34
|
+
options[:write_type]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Check if should cast value
|
38
|
+
def type_cast?
|
39
|
+
read_type != write_type
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get associated value
|
43
|
+
def association_record
|
44
|
+
record.send(association_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get association attribute value
|
48
|
+
def attribute_value(*args)
|
49
|
+
association_record.try(attribute_name, *args)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get record attribute type caster
|
53
|
+
def read_type_caster
|
54
|
+
lookup_type_caster(read_type)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get association attribute type caster
|
58
|
+
def write_type_caster
|
59
|
+
lookup_type_caster(write_type)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Cast value for reading
|
63
|
+
def cast_read_value(value)
|
64
|
+
read_type_caster.cast(value)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Cast value for writing
|
68
|
+
def cast_write_value(value)
|
69
|
+
write_type_caster.cast(value)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Prepare association attribute value for writing
|
73
|
+
def normalize_value(value)
|
74
|
+
value = cast_read_value(value)
|
75
|
+
cast_write_value(value)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Read and cast value
|
79
|
+
def read(*args)
|
80
|
+
value = attribute_value(*args) || default_value
|
81
|
+
type_cast? ? cast_read_value(value) : value
|
82
|
+
end
|
83
|
+
|
84
|
+
# Cast and write value
|
85
|
+
def write(value)
|
86
|
+
value = normalize_value(value) if type_cast?
|
87
|
+
association_record.send(:"#{attribute_name}=", value)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Get type caster class
|
93
|
+
def lookup_type_caster(type_caster)
|
94
|
+
if type_caster.is_a?(Symbol)
|
95
|
+
ActiveRecord::Type.lookup(type_caster)
|
96
|
+
else
|
97
|
+
type_caster
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ActiveDelegate
|
2
|
+
module Attribute
|
3
|
+
class Localize
|
4
|
+
attr_reader :attribute_name, :association_instance
|
5
|
+
|
6
|
+
# Initialize attribute methods
|
7
|
+
def initialize(attribute_name, association_class)
|
8
|
+
@attribute_name = attribute_name
|
9
|
+
@association_instance = association_class.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get localized attributes
|
13
|
+
def attributes
|
14
|
+
localized = suffixes.map { |s| :"#{attribute_name}#{s}" }
|
15
|
+
localized & association_instance.methods
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# Get localized method suffixes
|
21
|
+
def suffixes
|
22
|
+
@suffixes ||= I18n.available_locales.map do |locale|
|
23
|
+
"_#{normalize_locale(locale)}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Normalize locale
|
28
|
+
def normalize_locale(locale)
|
29
|
+
locale.to_s.downcase.sub('-', '_').to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ActiveDelegate
|
2
|
+
module Attribute
|
3
|
+
class Methods
|
4
|
+
attr_reader :attribute_name, :association_instance
|
5
|
+
|
6
|
+
# Initialize attribute methods
|
7
|
+
def initialize(attribute_name, association_class)
|
8
|
+
@attribute_name = attribute_name
|
9
|
+
@association_instance = association_class.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get accessor methods
|
13
|
+
def accessors
|
14
|
+
suffix_attribute(accessor_suffixes)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get dirty methods for attributes
|
18
|
+
def dirty
|
19
|
+
suffix_attribute(dirty_suffixes)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get delegatable methods
|
23
|
+
def delegatable
|
24
|
+
accessors + dirty
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Get accessor method suffixes
|
30
|
+
def accessor_suffixes
|
31
|
+
['', '=', '?']
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get dirty method suffixes
|
35
|
+
def dirty_suffixes
|
36
|
+
@dirty_suffixes ||= Class.new do
|
37
|
+
include ::ActiveModel::Dirty
|
38
|
+
end.attribute_method_matchers.map(&:suffix).select { |m| m =~ /\A_/ }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Generate suffixed array of symbols
|
42
|
+
def suffix_attribute(suffixes)
|
43
|
+
delegatable = suffixes.map { |s| :"#{attribute_name}#{s}" }
|
44
|
+
delegatable & association_instance.methods
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'active_delegate/attribute/methods'
|
2
|
+
require 'active_delegate/attribute/localize'
|
3
|
+
|
4
|
+
module ActiveDelegate
|
5
|
+
module Attribute
|
6
|
+
class Object
|
7
|
+
attr_reader :attribute_name, :association_class, :options
|
8
|
+
|
9
|
+
# Initialize attribute methods
|
10
|
+
def initialize(attribute_name, association_class, options = {})
|
11
|
+
@attribute_name = attribute_name
|
12
|
+
@association_class = association_class
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
# Check if should define attribute
|
17
|
+
def define?
|
18
|
+
options[:define] || in_option?(:define)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Check if should localize attribute
|
22
|
+
def localize?
|
23
|
+
options[:localized] || in_option?(:localized)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Check if should define attribute finders
|
27
|
+
def finder?
|
28
|
+
options[:finder] || in_option?(:finder)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Check if should define attribute scopes
|
32
|
+
def scope?
|
33
|
+
options[:scope] || in_option?(:scope)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get attribute prefix
|
37
|
+
def prefix
|
38
|
+
options[:prefix]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get attribute default
|
42
|
+
def default
|
43
|
+
options.fetch :default, association_class.column_defaults[unprefixed.to_s]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get read type or fallback to write type
|
47
|
+
def read_type
|
48
|
+
options.fetch :cast_type, write_type
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get write type from associated model
|
52
|
+
def write_type
|
53
|
+
association_class.type_for_attribute(unprefixed.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get unprefixed attribute
|
57
|
+
def unprefixed
|
58
|
+
remove_prefix(attribute_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get prefixed attribute
|
62
|
+
def prefixed
|
63
|
+
add_prefix(attribute_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Check if attribute is prefixed
|
67
|
+
def prefixed?
|
68
|
+
unprefixed != prefixed
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get aliased attribute
|
72
|
+
def aliased
|
73
|
+
options[:alias] || prefixed
|
74
|
+
end
|
75
|
+
|
76
|
+
# Check if attribute is aliased
|
77
|
+
def aliased?
|
78
|
+
prefixed != aliased
|
79
|
+
end
|
80
|
+
|
81
|
+
# Get method aliases
|
82
|
+
def aliases
|
83
|
+
return {} unless aliased?
|
84
|
+
Hash[delegatable_methods.map { |m| generate_alias(m) }]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get localized attributes
|
88
|
+
def localized
|
89
|
+
@localized ||= Localize.new(unprefixed, association_class).attributes
|
90
|
+
end
|
91
|
+
|
92
|
+
# Check if attributes has localized methods
|
93
|
+
def localized?
|
94
|
+
localized.any?
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get delegatable attributes
|
98
|
+
def delegatable_attributes
|
99
|
+
@delegatable_attributes ||= [unprefixed, *localized].map { |a| add_prefix(a) }
|
100
|
+
end
|
101
|
+
|
102
|
+
# Get delegatable attribute methods
|
103
|
+
def delegatable_methods
|
104
|
+
@delegatable_methods ||= [unprefixed, *localized].flat_map do |method_name|
|
105
|
+
Methods.new(method_name, association_class).delegatable
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Prefix attribute
|
112
|
+
def add_prefix(attr_name)
|
113
|
+
prefix.present? ? :"#{prefix}_#{attr_name}" : attr_name
|
114
|
+
end
|
115
|
+
|
116
|
+
# Unprefix attribute
|
117
|
+
def remove_prefix(attr_name)
|
118
|
+
attr_name.to_s.sub("#{prefix}_", '').to_sym
|
119
|
+
end
|
120
|
+
|
121
|
+
# Generate alias method
|
122
|
+
def generate_alias(method_name)
|
123
|
+
old_name = method_name.to_s.sub(unprefixed.to_s, prefixed.to_s)
|
124
|
+
new_name = method_name.to_s.sub(unprefixed.to_s, aliased.to_s)
|
125
|
+
|
126
|
+
[new_name.to_sym, old_name.to_sym]
|
127
|
+
end
|
128
|
+
|
129
|
+
# Check if attribute is in option
|
130
|
+
def in_option?(key)
|
131
|
+
attribute_name.in? Array(options[key])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -1,274 +1,175 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
autoload :Localized, 'active_delegate/localized'
|
5
|
-
|
6
|
-
class Attributes
|
7
|
-
# Initialize attributes
|
8
|
-
def initialize(model, options)
|
9
|
-
@model = model
|
10
|
-
@options = default_options.merge(options)
|
1
|
+
require 'active_delegate/delegator'
|
2
|
+
require 'active_delegate/attribute/object'
|
3
|
+
require 'active_delegate/attribute/accessor'
|
11
4
|
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
module ActiveDelegate
|
6
|
+
class Attributes < Delegator
|
7
|
+
# Get default options
|
8
|
+
def default_options
|
9
|
+
{
|
10
|
+
except: [],
|
11
|
+
only: [],
|
12
|
+
localized: false,
|
13
|
+
define: true,
|
14
|
+
finder: false,
|
15
|
+
scope: false,
|
16
|
+
to: nil,
|
17
|
+
prefix: nil,
|
18
|
+
allow_nil: false
|
19
|
+
}
|
15
20
|
end
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
except: [], only: [], allow_nil: false, to: [],
|
23
|
-
prefix: nil, localized: false, finder: false,
|
24
|
-
scope: false, cast: false
|
25
|
-
}
|
26
|
-
end
|
27
|
-
|
28
|
-
# Get association reflection
|
29
|
-
def association_reflection
|
30
|
-
assoc_name = @options.fetch(:to)
|
31
|
-
reflection = @model.reflect_on_association(assoc_name)
|
32
|
-
|
33
|
-
return reflection unless reflection.nil?
|
34
|
-
raise "#{@model.name} don't have the association #{assoc_name}"
|
35
|
-
end
|
36
|
-
|
37
|
-
# Get model association class
|
38
|
-
def association_class
|
39
|
-
association_reflection.klass
|
40
|
-
end
|
41
|
-
|
42
|
-
# Get association attribute names
|
43
|
-
def association_attribute_names
|
44
|
-
association_class.attribute_names
|
45
|
-
end
|
46
|
-
|
47
|
-
# Default excluded attributes
|
48
|
-
def default_excluded_attributes
|
49
|
-
assoc_as = association_reflection.options[:as]
|
50
|
-
poly_attr = [:"#{assoc_as}_type", :"#{assoc_as}_id"] if assoc_as.present?
|
51
|
-
|
52
|
-
[:id, :created_at, :updated_at] + poly_attr.to_a
|
53
|
-
end
|
54
|
-
|
55
|
-
# Get delegatable attributes
|
56
|
-
def delegatable_attributes
|
57
|
-
attributes = association_attribute_names.map(&:to_sym)
|
58
|
-
attributes = attributes & @options[:only].to_a if @options[:only].present?
|
59
|
-
attributes = attributes - @options[:except].to_a if @options[:except].present?
|
60
|
-
attributes = attributes - default_excluded_attributes
|
61
|
-
|
62
|
-
attributes.map(&:to_sym)
|
63
|
-
end
|
22
|
+
# Get attribute options
|
23
|
+
def attribute_options
|
24
|
+
keys = %i[cast_type default define alias localized finder scope]
|
25
|
+
options.select { |k, _v| k.in? keys }.merge(prefix: delegation_prefix)
|
26
|
+
end
|
64
27
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
28
|
+
# Get association table
|
29
|
+
def association_table
|
30
|
+
association_class.table_name
|
31
|
+
end
|
69
32
|
|
70
|
-
|
71
|
-
|
33
|
+
# Default excluded attributes
|
34
|
+
def excluded_attributes
|
35
|
+
excluded = %i[id created_at updated_at]
|
72
36
|
|
73
|
-
|
74
|
-
|
75
|
-
attributes = delegatable_attributes + localized_attributes
|
76
|
-
readwrite = ReadWrite.readwrite_methods(attributes)
|
77
|
-
dirty = Dirty.dirty_methods(attributes)
|
78
|
-
methods = readwrite + dirty
|
37
|
+
sti_col = association_class.inheritance_column
|
38
|
+
excluded << sti_col.to_sym if sti_col.present?
|
79
39
|
|
80
|
-
|
81
|
-
|
40
|
+
assoc_as = association_reflection.options[:as]
|
41
|
+
excluded += [:"#{assoc_as}_type", :"#{assoc_as}_id"] if assoc_as.present?
|
82
42
|
|
83
|
-
|
84
|
-
|
85
|
-
options = { to: @options[:to], allow_nil: @options[:allow_nil], prefix: @options[:prefix] }
|
86
|
-
@model.delegate(*delegatable_methods, options)
|
87
|
-
end
|
43
|
+
excluded
|
44
|
+
end
|
88
45
|
|
89
|
-
|
90
|
-
|
91
|
-
|
46
|
+
# Get delegatable attributes
|
47
|
+
def delegatable_attributes
|
48
|
+
attributes = delegation_args(association_class.attribute_names)
|
49
|
+
attributes -= excluded_attributes
|
92
50
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
98
|
-
EOM
|
99
|
-
end
|
51
|
+
attributes.map! do |attribute_name|
|
52
|
+
ActiveDelegate::Attribute::Object.new(
|
53
|
+
attribute_name, association_class, attribute_options
|
54
|
+
)
|
100
55
|
end
|
101
56
|
|
102
|
-
|
103
|
-
|
104
|
-
prefix = @options[:prefix]
|
105
|
-
prefix.is_a?(TrueClass) ? @options[:to] : prefix
|
106
|
-
end
|
57
|
+
attributes.reject { |a| model.has_attribute?(a.prefixed) }
|
58
|
+
end
|
107
59
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
60
|
+
# Delegate attributes
|
61
|
+
def call
|
62
|
+
redefine_build_association(association_name)
|
112
63
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
attributes.map { |a| :"#{attribute_prefix}_#{a}" }
|
117
|
-
else
|
118
|
-
attributes
|
119
|
-
end
|
120
|
-
end
|
64
|
+
delegatable_attributes.each do |attribute|
|
65
|
+
delegate_methods(attribute.delegatable_methods)
|
66
|
+
define_model_class_methods(attribute)
|
121
67
|
|
122
|
-
|
123
|
-
|
124
|
-
@options.fetch :default, association_class.column_defaults["#{attribute}"]
|
68
|
+
define_attribute_methods(attribute)
|
69
|
+
define_attribute_queries(attribute)
|
125
70
|
end
|
71
|
+
end
|
126
72
|
|
127
|
-
|
128
|
-
def attribute_cast_type(attribute)
|
129
|
-
@options.fetch :cast_type, association_class.type_for_attribute("#{attribute}")
|
130
|
-
end
|
73
|
+
private
|
131
74
|
|
132
|
-
|
133
|
-
|
134
|
-
|
75
|
+
# Redefine build association method
|
76
|
+
def redefine_build_association(assoc_name)
|
77
|
+
model.class_eval do
|
78
|
+
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
79
|
+
def #{assoc_name}
|
80
|
+
super || send(:build_#{assoc_name})
|
81
|
+
end
|
82
|
+
EOM
|
135
83
|
end
|
84
|
+
end
|
136
85
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
86
|
+
# Redefine attribute accessor methods
|
87
|
+
def redefine_attribute_accessors(method_name, attribute)
|
88
|
+
attr_options = {
|
89
|
+
association: association_name,
|
90
|
+
attribute: attribute.unprefixed,
|
91
|
+
read_type: attribute.read_type,
|
92
|
+
write_type: attribute.write_type,
|
93
|
+
default: attribute.default
|
94
|
+
}
|
141
95
|
|
142
|
-
|
143
|
-
|
144
|
-
@options[:finder] || Array(@options[:finder]).include?(attribute)
|
96
|
+
model.send(:redefine_method, method_name) do |*args|
|
97
|
+
ActiveDelegate::Attribute::Accessor.new(self, attr_options).read(*args)
|
145
98
|
end
|
146
99
|
|
147
|
-
#
|
148
|
-
|
149
|
-
@options[:scope] || Array(@options[:scope]).include?(attribute)
|
100
|
+
model.send(:redefine_method, :"#{method_name}=") do |value|
|
101
|
+
ActiveDelegate::Attribute::Accessor.new(self, attr_options).write(value)
|
150
102
|
end
|
103
|
+
end
|
151
104
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
105
|
+
# Delegate attribute methods
|
106
|
+
def delegate_methods(methods)
|
107
|
+
model.delegate(*methods, delegation_options)
|
108
|
+
end
|
156
109
|
|
157
|
-
|
158
|
-
|
110
|
+
# Define model method keeping old values
|
111
|
+
def define_model_method(method, *attributes)
|
112
|
+
attributes = model.try(method).to_a.concat(attributes).uniq
|
113
|
+
model.send(:define_singleton_method, method) { attributes }
|
114
|
+
end
|
159
115
|
|
160
|
-
|
161
|
-
|
116
|
+
# Store attribute names in model class methods
|
117
|
+
def define_model_class_methods(attribute)
|
118
|
+
method_name = :"#{association_table}_attribute_names"
|
119
|
+
define_model_method(method_name, attribute.prefixed)
|
162
120
|
|
163
|
-
|
164
|
-
|
165
|
-
|
121
|
+
method_name = :"#{association_table}_localized_attribute_names"
|
122
|
+
define_model_method(method_name, attribute.prefixed) if attribute.localized?
|
123
|
+
end
|
166
124
|
|
167
|
-
|
168
|
-
|
125
|
+
# Define delegated attribute methods
|
126
|
+
def define_attribute_methods(attribute)
|
127
|
+
if attribute.define?
|
128
|
+
model.attribute(attribute.aliased, attribute.read_type)
|
169
129
|
end
|
170
130
|
|
171
|
-
|
172
|
-
|
173
|
-
existing = @model.attribute_names.map(&:to_sym)
|
174
|
-
undefined = attributes.reject { |a| a.in? existing }
|
175
|
-
|
176
|
-
undefined.each do |attrib|
|
177
|
-
attr_name = unprefix_attribute(attrib)
|
178
|
-
|
179
|
-
define_attribute_default_value(attrib, attr_name)
|
180
|
-
define_attribute_type_cast(attrib, attr_name)
|
181
|
-
define_attribute_and_alias(attrib, attr_name)
|
182
|
-
define_attribute_finders_and_scopes(attrib, attr_name)
|
183
|
-
end
|
131
|
+
attribute.delegatable_attributes.each do |method_name|
|
132
|
+
redefine_attribute_accessors(method_name, attribute)
|
184
133
|
end
|
185
134
|
|
186
|
-
|
187
|
-
|
188
|
-
attr_default = attribute_default(attr_name)
|
189
|
-
|
190
|
-
unless attr_default.nil?
|
191
|
-
attr_assoc = @options[:to]
|
192
|
-
attr_cattr = :"_attribute_#{attrib}_default"
|
193
|
-
|
194
|
-
@model.send(:define_singleton_method, attr_cattr) { attr_default }
|
195
|
-
|
196
|
-
@model.class_eval do
|
197
|
-
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
198
|
-
def #{attrib}
|
199
|
-
send(:#{attr_assoc}).try(:#{attr_name}) || self.class.send(:#{attr_cattr})
|
200
|
-
end
|
201
|
-
EOM
|
202
|
-
end
|
203
|
-
end
|
135
|
+
attribute.aliases.each do |alias_name, method_name|
|
136
|
+
model.alias_method(alias_name, method_name)
|
204
137
|
end
|
138
|
+
end
|
205
139
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
if needs_type_cast?(attr_name)
|
212
|
-
@model.class_eval do
|
213
|
-
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
214
|
-
def #{attrib}
|
215
|
-
assoc_value = send(:#{attr_assoc}).try(:#{attr_name})
|
216
|
-
self.class.type_for_attribute('#{attrib}').cast(assoc_value) ||
|
217
|
-
self.class.try(:#{attr_cattr})
|
218
|
-
end
|
219
|
-
|
220
|
-
def #{attrib}=(value)
|
221
|
-
assoc_value = self.class.type_for_attribute('#{attrib}').cast(value)
|
222
|
-
send(:#{attr_assoc}).send(:#{attr_name}=, assoc_value)
|
223
|
-
end
|
224
|
-
EOM
|
225
|
-
end
|
226
|
-
end
|
140
|
+
# Define attribute finder methods
|
141
|
+
def define_attribute_finders(attr_method:, attr_column:, assoc_name:, table_name:)
|
142
|
+
model.send(:define_singleton_method, :"find_by_#{attr_method}") do |value|
|
143
|
+
joins(assoc_name).find_by(table_name => { attr_column => value })
|
227
144
|
end
|
228
145
|
|
229
|
-
#
|
230
|
-
|
231
|
-
cast_type = attribute_cast_type(attr_name)
|
232
|
-
attr_alias = @options[:alias]
|
233
|
-
|
234
|
-
@model.attribute(attrib, cast_type)
|
235
|
-
|
236
|
-
unless attr_alias.nil?
|
237
|
-
@model.attribute(attr_alias, cast_type)
|
238
|
-
@model.alias_attribute(attr_alias, attrib)
|
239
|
-
end
|
146
|
+
model.send(:define_singleton_method, :"find_by_#{attr_method}!") do |value|
|
147
|
+
joins(assoc_name).find_by!(table_name => { attr_column => value })
|
240
148
|
end
|
149
|
+
end
|
241
150
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
attr_args = [@options[:alias] || attrib, attr_name, attr_assoc, attr_table]
|
247
|
-
|
248
|
-
define_attribute_finder_methods(*attr_args) if define_finders?(attr_name)
|
249
|
-
define_attribute_scope_methods(*attr_args) if define_scopes?(attr_name)
|
151
|
+
# Define attribute scope methods
|
152
|
+
def define_attribute_scopes(attr_method:, attr_column:, assoc_name:, table_name:)
|
153
|
+
model.send(:define_singleton_method, :"with_#{attr_method}") do |*args|
|
154
|
+
joins(assoc_name).where(table_name => { attr_column => args })
|
250
155
|
end
|
251
156
|
|
252
|
-
#
|
253
|
-
|
254
|
-
@model.send(:define_singleton_method, :"find_by_#{attrib}") do |value|
|
255
|
-
joins(attr_assoc).find_by(attr_table => { attr_name => value })
|
256
|
-
end
|
257
|
-
|
258
|
-
@model.send(:define_singleton_method, :"find_by_#{attrib}!") do |value|
|
259
|
-
joins(attr_assoc).find_by!(attr_table => { attr_name => value })
|
260
|
-
end
|
157
|
+
model.send(:define_singleton_method, :"without_#{attr_method}") do |*args|
|
158
|
+
joins(assoc_name).where.not(table_name => { attr_column => args })
|
261
159
|
end
|
160
|
+
end
|
262
161
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
162
|
+
# Define attribute finders and scopes
|
163
|
+
def define_attribute_queries(attribute)
|
164
|
+
attr_options = {
|
165
|
+
assoc_name: association_name,
|
166
|
+
table_name: association_table,
|
167
|
+
attr_method: attribute.aliased,
|
168
|
+
attr_column: attribute.unprefixed
|
169
|
+
}
|
170
|
+
|
171
|
+
define_attribute_finders(attr_options) if attribute.finder?
|
172
|
+
define_attribute_scopes(attr_options) if attribute.scope?
|
173
|
+
end
|
273
174
|
end
|
274
175
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ActiveDelegate
|
2
|
+
class Delegator
|
3
|
+
attr_reader :model, :options
|
4
|
+
|
5
|
+
# Initialize delegator
|
6
|
+
def initialize(model, options)
|
7
|
+
@model = model
|
8
|
+
@options = default_options.merge(options.symbolize_keys)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Get default options
|
12
|
+
def default_options
|
13
|
+
{
|
14
|
+
except: [],
|
15
|
+
only: [],
|
16
|
+
to: nil,
|
17
|
+
allow_nil: false
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Get delegation options
|
22
|
+
def delegation_options
|
23
|
+
options.select { |k, _v| k.in? %i[to allow_nil prefix] }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Get delegation arguments
|
27
|
+
def delegation_args(available = [])
|
28
|
+
included = Array(options[:only]).map(&:to_sym)
|
29
|
+
excluded = Array(options[:except]).map(&:to_sym)
|
30
|
+
available = Array(available).map(&:to_sym)
|
31
|
+
available &= included if included.any?
|
32
|
+
available -= excluded if excluded.any?
|
33
|
+
|
34
|
+
available
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get delegation prefix
|
38
|
+
def delegation_prefix
|
39
|
+
prefix = options[:prefix]
|
40
|
+
prefix == true ? association_name : prefix
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get association name
|
44
|
+
def association_name
|
45
|
+
@options[:to]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get association reflection
|
49
|
+
def association_reflection
|
50
|
+
reflection = @model.reflect_on_association(association_name)
|
51
|
+
return reflection unless reflection.nil?
|
52
|
+
raise "#{@model.name} don't have the association #{association_name}"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get model association class
|
56
|
+
def association_class
|
57
|
+
association_reflection.klass
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_delegate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonian Guveli
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-10-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -53,33 +53,33 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.14'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '5.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '5.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '10.0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '10.0'
|
83
83
|
description: Stores and retrieves delegatable data through attributes on an ActiveRecord
|
84
84
|
class, with support for translatable attributes.
|
85
85
|
email:
|
@@ -94,11 +94,14 @@ files:
|
|
94
94
|
- README.md
|
95
95
|
- Rakefile
|
96
96
|
- lib/active_delegate.rb
|
97
|
+
- lib/active_delegate/association/methods.rb
|
97
98
|
- lib/active_delegate/associations.rb
|
99
|
+
- lib/active_delegate/attribute/accessor.rb
|
100
|
+
- lib/active_delegate/attribute/localize.rb
|
101
|
+
- lib/active_delegate/attribute/methods.rb
|
102
|
+
- lib/active_delegate/attribute/object.rb
|
98
103
|
- lib/active_delegate/attributes.rb
|
99
|
-
- lib/active_delegate/
|
100
|
-
- lib/active_delegate/localized.rb
|
101
|
-
- lib/active_delegate/read_write.rb
|
104
|
+
- lib/active_delegate/delegator.rb
|
102
105
|
- lib/active_delegate/version.rb
|
103
106
|
homepage: https://github.com/hardpixel/active-delegate
|
104
107
|
licenses:
|
@@ -120,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
123
|
version: '0'
|
121
124
|
requirements: []
|
122
125
|
rubyforge_project:
|
123
|
-
rubygems_version: 2.
|
126
|
+
rubygems_version: 2.7.7
|
124
127
|
signing_key:
|
125
128
|
specification_version: 4
|
126
129
|
summary: Delegate ActiveRecord model attributes and associations
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module ActiveDelegate
|
2
|
-
module Dirty
|
3
|
-
class << self
|
4
|
-
# Get dirty methods for attributes
|
5
|
-
def dirty_methods(attributes)
|
6
|
-
@dirty_methods = attributes.to_a.flat_map do |attribute|
|
7
|
-
method_suffixes.map { |suffix| "#{attribute}#{suffix}" }
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
# Get method suffixes
|
12
|
-
def method_suffixes
|
13
|
-
@method_suffixes ||= Class.new do
|
14
|
-
include ::ActiveModel::Dirty
|
15
|
-
end.attribute_method_matchers.map(&:suffix).select { |m| m =~ /\A_/ }
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
require 'i18n'
|
2
|
-
|
3
|
-
module ActiveDelegate
|
4
|
-
module Localized
|
5
|
-
class << self
|
6
|
-
# Get localized methods for attributes
|
7
|
-
def localized_methods(attributes)
|
8
|
-
@localized_methods = attributes.to_a.flat_map do |attribute|
|
9
|
-
method_suffixes.map { |suffix| "#{attribute}#{suffix}" }
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
# Get method suffixes
|
14
|
-
def method_suffixes
|
15
|
-
@method_suffixes ||= I18n.available_locales.map do |locale|
|
16
|
-
"_#{normalize_locale(locale)}"
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# Normalize locale
|
21
|
-
def normalize_locale(locale)
|
22
|
-
"#{locale.to_s.downcase.sub("-", "_")}".freeze
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module ActiveDelegate
|
2
|
-
module ReadWrite
|
3
|
-
class << self
|
4
|
-
# Get readwrite methods for attributes
|
5
|
-
def readwrite_methods(attributes)
|
6
|
-
@readwrite_methods = attributes.to_a.flat_map do |attribute|
|
7
|
-
method_suffixes.map { |suffix| "#{attribute}#{suffix}" }
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
# Get method suffixes
|
12
|
-
def method_suffixes
|
13
|
-
['', '=', '?']
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|