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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: db4ce7e906bb62440ecead0e66d21d611b9354ac
4
- data.tar.gz: e6e9e1b63faaf10d0f7522f7c19cc22563976554
2
+ SHA256:
3
+ metadata.gz: 7ef4539c2b70f925e9e83e293bd7fec0a560586526efa6c6b486f36b1675fa65
4
+ data.tar.gz: ff272b205a8757d99b95cc45642b0f7c10cbb4171e63b3dd9149ab82389b03bd
5
5
  SHA512:
6
- metadata.gz: 11d848253fb51ced3c37e97806dcd15ef71634a501ae6003a65e18a074347406d1b1c12241904b74d58b5ebc37a386d97baf119250fe83fcd0d16ad26686265d
7
- data.tar.gz: 51202fe31b97ac2fa6bf6dfa0966681d9b75fc87ef3845b9959674f59403c6f9d7dce06a62682175c14fee417f7cdfdc000c51798557f53a8c1001925dd55df9
6
+ metadata.gz: 620de75b4495080c73c9059ff361d2550ad2b2c031c19f68745f53b375dc1df119e18c33ab5f22027ae08cd9bb0e7b919740e87f0955f084133e580ce64fc638
7
+ data.tar.gz: 1c4e5f6b8f3cc93212b027d47e3098c1bd96a5c9b34bca9d4f00204892f8ab8a72e7dae65e407dc4d186860a3ba88448b2c814b5308068949418df5047ce57a0
@@ -1,28 +1,29 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_delegate (0.1.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.1)
12
- activesupport (= 5.1.1)
13
- activerecord (5.1.1)
14
- activemodel (= 5.1.1)
15
- activesupport (= 5.1.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.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.8.4)
25
- minitest (5.10.2)
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.14.6
42
+ 1.15.4
data/Rakefile CHANGED
@@ -7,4 +7,4 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList['test/**/*_test.rb']
8
8
  end
9
9
 
10
- task :default => :test
10
+ task default: :test
@@ -12,23 +12,31 @@ module ActiveDelegate
12
12
  # Delegate associations
13
13
  def delegate_associations(*args)
14
14
  options = args.extract_options!
15
- Associations.new(self, options)
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.except(:single, :cast_type, :alias)
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: [attribute], single: true, cast_type: cast_type)
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
- module ActiveDelegate
2
- class Associations
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
- delegate_associations
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
- private
12
-
13
- # Get default options
14
- def default_options
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
- delegatable_associations.each do |association|
62
- @model.delegate "#{association}", options
63
- @model.delegate "#{association}=", options
16
+ # Get delegatable associations
17
+ def delegatable_associations
18
+ delegation_args(association_names)
19
+ end
64
20
 
65
- @model.delegate "#{association}_attributes=", options rescue true
66
- @model.delegate "build_#{association}", options unless collection_association?(association)
67
- end
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
- module ActiveDelegate
2
- autoload :ReadWrite, 'active_delegate/read_write'
3
- autoload :Dirty, 'active_delegate/dirty'
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
- delegate_attributes
13
- save_delegated_attributes
14
- redefine_build_association
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
- private
18
-
19
- # Get default options
20
- def default_options
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
- # Get localized delegatable attributes
66
- def localized_attributes
67
- attributes = delegatable_attributes
68
- localized = Localized.localized_methods(attributes) if @options[:localized].present?
28
+ # Get association table
29
+ def association_table
30
+ association_class.table_name
31
+ end
69
32
 
70
- localized.to_a.map(&:to_sym)
71
- end
33
+ # Default excluded attributes
34
+ def excluded_attributes
35
+ excluded = %i[id created_at updated_at]
72
36
 
73
- # Get delegatable methods
74
- def delegatable_methods
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
- methods.map(&:to_sym)
81
- end
40
+ assoc_as = association_reflection.options[:as]
41
+ excluded += [:"#{assoc_as}_type", :"#{assoc_as}_id"] if assoc_as.present?
82
42
 
83
- # Delegate attributes
84
- def delegate_attributes
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
- # Redefine build association method
90
- def redefine_build_association
91
- assoc_name = @options[:to]
46
+ # Get delegatable attributes
47
+ def delegatable_attributes
48
+ attributes = delegation_args(association_class.attribute_names)
49
+ attributes -= excluded_attributes
92
50
 
93
- @model.class_eval do
94
- class_eval <<-EOM, __FILE__, __LINE__ + 1
95
- def #{assoc_name}
96
- super || send(:build_#{assoc_name})
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
- # Get attribute prefix
103
- def attribute_prefix
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
- # Get unprefixed attribute
109
- def unprefix_attribute(attribute)
110
- attribute.to_s.sub("#{attribute_prefix}_", '')
111
- end
60
+ # Delegate attributes
61
+ def call
62
+ redefine_build_association(association_name)
112
63
 
113
- # Get prefixed attributes
114
- def prefix_attributes(attributes)
115
- if @options[:prefix].present?
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
- # Get attribute default
123
- def attribute_default(attribute)
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
- # Get attribute cast type
128
- def attribute_cast_type(attribute)
129
- @options.fetch :cast_type, association_class.type_for_attribute("#{attribute}")
130
- end
73
+ private
131
74
 
132
- # Check if attribute types are not the same
133
- def cast_types_mismatch?(attribute)
134
- attribute_cast_type(attribute) != association_class.type_for_attribute("#{attribute}")
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
- # Check if attribute needs type cast
138
- def needs_type_cast?(attribute)
139
- @options[:cast] != false && cast_types_mismatch?(attribute)
140
- end
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
- # Check if should define attribute finders
143
- def define_finders?(attribute)
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
- # Check if should define attribute scopes
148
- def define_scopes?(attribute)
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
- # Save delagated attributes in model class
153
- def save_delegated_attributes
154
- dl_atable = association_reflection.klass.table_name
155
- dl_method = :"#{dl_atable}_attribute_names"
105
+ # Delegate attribute methods
106
+ def delegate_methods(methods)
107
+ model.delegate(*methods, delegation_options)
108
+ end
156
109
 
157
- delegated = prefix_attributes(delegatable_attributes)
158
- define_attribute_defaults_and_methods(delegated)
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
- delegated = @model.try(dl_method).to_a.concat(delegated)
161
- @model.send(:define_singleton_method, dl_method) { delegated }
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
- if @options[:localized].present?
164
- localized = prefix_attributes(localized_attributes)
165
- lc_method = :"#{dl_atable}_localized_attribute_names"
121
+ method_name = :"#{association_table}_localized_attribute_names"
122
+ define_model_method(method_name, attribute.prefixed) if attribute.localized?
123
+ end
166
124
 
167
- @model.send(:define_singleton_method, lc_method) { localized }
168
- end
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
- # Define attribute default values, methods and scopes
172
- def define_attribute_defaults_and_methods(attributes)
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
- # Define delegated attribute default
187
- def define_attribute_default_value(attrib, attr_name)
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
- # Define attribute type casting
207
- def define_attribute_type_cast(attrib, attr_name)
208
- attr_assoc = @options[:to]
209
- attr_cattr = :"_attribute_#{attrib}_default"
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
- # Define delegated attribute alias
230
- def define_attribute_and_alias(attrib, attr_name)
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
- # Define attribute finders and scopes
243
- def define_attribute_finders_and_scopes(attrib, attr_name)
244
- attr_assoc = @options[:to]
245
- attr_table = association_reflection.klass.table_name
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
- # Define attribute finder methods
253
- def define_attribute_finder_methods(attrib, attr_name, attr_assoc, attr_table)
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
- # Define attribute scope methods
264
- def define_attribute_scope_methods(attrib, attr_name, attr_assoc, attr_table)
265
- @model.scope :"with_#{attrib}", -> (*names) do
266
- joins(attr_assoc).where(attr_table => { attr_name => names })
267
- end
268
-
269
- @model.scope :"without_#{attrib}", -> (*names) do
270
- joins(attr_assoc).where.not(attr_table => { attr_name => names })
271
- end
272
- end
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
@@ -1,3 +1,3 @@
1
1
  module ActiveDelegate
2
- VERSION = '0.2.1'
2
+ VERSION = '1.0.0'
3
3
  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.2.1
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: 2017-12-11 00:00:00.000000000 Z
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: rake
56
+ name: minitest
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '10.0'
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: '10.0'
68
+ version: '5.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: minitest
70
+ name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '5.0'
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: '5.0'
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/dirty.rb
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.6.13
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