active_delegate 0.2.1 → 1.0.0

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