attribute-filters 1.4.0 → 2.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.
Files changed (41) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.yardopts +1 -0
  3. data/ChangeLog +501 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +7 -7
  6. data/Manifest.txt +7 -0
  7. data/README.md +59 -30
  8. data/Rakefile +1 -0
  9. data/attribute-filters.gemspec +4 -4
  10. data/docs/COMMON-FILTERS.md +1067 -0
  11. data/docs/HISTORY +42 -0
  12. data/docs/TODO +29 -12
  13. data/docs/USAGE.md +718 -818
  14. data/lib/attribute-filters.rb +4 -2
  15. data/lib/attribute-filters/attribute_set.rb +144 -73
  16. data/lib/attribute-filters/attribute_set_annotations.rb +110 -77
  17. data/lib/attribute-filters/attribute_set_attrquery.rb +51 -8
  18. data/lib/attribute-filters/attribute_set_enum.rb +44 -38
  19. data/lib/attribute-filters/attribute_set_query.rb +62 -12
  20. data/lib/attribute-filters/backports.rb +36 -4
  21. data/lib/attribute-filters/common_filters.rb +83 -37
  22. data/lib/attribute-filters/common_filters/bare.rb +29 -0
  23. data/lib/attribute-filters/common_filters/case.rb +34 -19
  24. data/lib/attribute-filters/common_filters/convert.rb +259 -0
  25. data/lib/attribute-filters/common_filters/join.rb +37 -30
  26. data/lib/attribute-filters/common_filters/order.rb +105 -0
  27. data/lib/attribute-filters/common_filters/pick.rb +90 -0
  28. data/lib/attribute-filters/common_filters/presence.rb +70 -0
  29. data/lib/attribute-filters/common_filters/split.rb +37 -21
  30. data/lib/attribute-filters/common_filters/squeeze.rb +28 -9
  31. data/lib/attribute-filters/common_filters/strip.rb +7 -3
  32. data/lib/attribute-filters/dsl_attr_virtual.rb +2 -1
  33. data/lib/attribute-filters/dsl_filters.rb +14 -61
  34. data/lib/attribute-filters/dsl_sets.rb +238 -88
  35. data/lib/attribute-filters/helpers.rb +7 -1
  36. data/lib/attribute-filters/meta_set.rb +38 -0
  37. data/lib/attribute-filters/version.rb +1 -1
  38. data/spec/attribute-filters_spec.rb +178 -16
  39. data/spec/spec_helper.rb +9 -4
  40. metadata +129 -69
  41. metadata.gz.sig +0 -0
@@ -21,21 +21,25 @@ module ActiveModel
21
21
  # +should_be_stripped+. It operates directly on attribute's contents.
22
22
  #
23
23
  # @note If a value of currently processed attribute is an array
24
- # then any element of the array is changed.
24
+ # then any element of the array is changed. The same with hash (its values are changed).
25
25
  #
26
26
  # @return [void]
27
27
  def strip_attributes
28
28
  filter_attrs_from_set(:should_be_stripped) do |atr|
29
- AttributeFiltersHelpers.each_element(atr, String) { |v| v.strip }
29
+ AFHelpers.each_element(atr, String) { |v| v.strip }
30
30
  end
31
31
  end
32
+ filtering_method :strip_attributes, :should_be_stripped
33
+
32
34
  # This submodule contains class methods used to easily define filter.
33
35
  module ClassMethods
34
36
  # Registers attributes that should be stripped.
35
37
  def strip_attributes(*args)
36
38
  attributes_that(:should_be_stripped, args)
37
39
  end
38
- alias_method :strip_attribute, :strip_attributes
40
+ alias_method :strip_attribute, :strip_attributes
41
+ alias_method :strips_attribute, :strip_attributes
42
+ alias_method :strips_attributes, :strip_attributes
39
43
  end # module ClassMethods
40
44
  end # module Strip
41
45
 
@@ -25,7 +25,7 @@ module ActiveModel
25
25
  writer_name = "#{atr_name}="
26
26
  atr_name = atr_name.to_sym
27
27
  attr_reader(atr_name) unless method_defined?(atr_name)
28
- attr_accessible(atr_name) if method_defined?(:attr_accessible)
28
+ treat_as_virtual(atr_name)
29
29
  if method_defined?(writer_name)
30
30
  self.class_eval <<-EVAL
31
31
  alias_method :#{atr_name}_without_change_tracking=, :#{writer_name}
@@ -43,6 +43,7 @@ module ActiveModel
43
43
  EVAL
44
44
  end
45
45
  end
46
+ nil
46
47
  end # def attr_virtual
47
48
  end # unless method_defined?(:attr_virtual)
48
49
  end # module ClassMethods
@@ -17,6 +17,9 @@ module ActiveModel
17
17
  :include_missing => false
18
18
  }.freeze
19
19
 
20
+ # Helpers module shortcut.
21
+ AFHelpers = AttributeFiltersHelpers
22
+
20
23
  # Gets names of attributes for which filters should be applied by
21
24
  # selecting attributes that are meeting certain criteria and belong
22
25
  # to the given attribute set.
@@ -24,28 +27,26 @@ module ActiveModel
24
27
  # @overload attributes_to_filter(set_name, process_all, no_presence_check)
25
28
  # @param set_name [String,Symbol] name of a set of attributes used to get attributes
26
29
  # @param process_all [Boolean] if set then all the attributes from the attribute set are selected,
27
- # not just attributes that have changed (defaults to +false+)
28
- # @param no_presence_check [Boolean] if set then the checking whether attribute exists will be
30
+ # not just the attributes that have changed (defaults to +false+)
31
+ # @param no_presence_check [Boolean] if set then the checking whether each attribute exists will be
29
32
  # disabled (matters only when +process_all+ is also set) (defaults to +false+)
30
33
  # @return [AttributeSet] set of attributes (attribute name => previous_value)
31
34
  #
32
35
  # @overload attributes_to_filter(attribute_set, process_all, no_presence_check)
33
36
  # @param attribute_set [AttributeSet] set of attributes used to get attributes
34
37
  # @param process_all [Boolean] if set then all the attributes from the attribute set are selected,
35
- # not just attributes that has changed (defaults to +false+)
36
- # @param no_presence_check [Boolean] if set then the checking whether attribute exists will be
38
+ # not just the attributes that has changed (defaults to +false+)
39
+ # @param no_presence_check [Boolean] if set then the checking whether each attribute exists will be
37
40
  # disabled (matters only when +process_all+ is also set) (defaults to +false+)
38
41
  # @return [AttributeSet] set of attributes (attribute name => previous_value)
39
42
  def attributes_to_filter(set_name, process_all = false, no_presence_check = false)
40
- atf = set_name.is_a?(::ActiveModel::AttributeSet) ? set_name : attribute_set_simple(set_name)
43
+ set_name.blank? and return ::ActiveModel::AttributeSet.new
44
+ atf = set_name.is_a?(::ActiveModel::AttributeSet) ? set_name : self.class.send(:__attribute_sets)[set_name.to_sym]
41
45
  if process_all
42
- no_presence_check ? atf : atf & all_attributes_simple(no_presence_check)
46
+ no_presence_check ? atf.deep_dup : atf & __all_attributes(false)
43
47
  else
44
- if self.class.filter_virtual_attributes_that_changed?
45
- atf & changes.keys
46
- else
47
- atf & (__vatrf(no_presence_check) + changes.keys)
48
- end
48
+ sr = self.class.send(:__attribute_filters_semi_real)
49
+ atf & ((no_presence_check ? sr : sr.select_accessible(self)) + changes.keys)
49
50
  end
50
51
  end
51
52
 
@@ -141,7 +142,7 @@ module ActiveModel
141
142
  # * +:no_presence_check+ – tells not to check for existence of each processed attribute when processing
142
143
  # all attributes; increases performance but you must care about putting into set only the existing attributes
143
144
  # * +:include_missing+ – includes attributes that does not exist in a resulting iteration (their values are
144
- # always +nil+); has effect only when +process_blank+ and +no_presence_check+ are set to +true+
145
+ # always +nil+); has the effect only when +process_blank+ and +no_presence_check+ are set
145
146
  # @yield [attribute_value, set_name, attribute_name, *args] block that will be called for each attribute
146
147
  # @yieldparam attribute_value [Object] current attribute value that should be altered
147
148
  # @yieldparam attribute_name [String] a name of currently processed attribute
@@ -174,60 +175,12 @@ module ActiveModel
174
175
  alias_method :for_attributes_that_are, :for_each_attr_from_set
175
176
  alias_method :for_attributes_which_are, :for_each_attr_from_set
176
177
 
177
- module ClassMethods
178
- # @overload treat_as_real(*attributes)
179
- # Informs Attribute Filters that the given attributes
180
- # should be treated as present, even they are not in
181
- # attributes hash provided by ORM or ActiveModel.
182
- # Useful when operating on virtual attributes.
183
- #
184
- # @param attributes [Array] list of attribute names
185
- # @return [void]
186
- #
187
- # @overload treat_as_real()
188
- # Gets the memorized attribute names that should be
189
- # treated as existing.
190
- #
191
- # @return [AttributeSet] set of attribute name
192
- def treat_as_real(*args)
193
- return __treat_as_real.dup if args.blank?
194
- __treat_as_real << args.flatten.compact.map { |atr| atr.to_s }
195
- nil
196
- end
197
- alias_method :treat_attribute_as_real, :treat_as_real
198
- alias_method :treat_attributes_as_real, :treat_as_real
199
-
200
- # Sets the internal flag that causes to check virtual attributes
201
- # for changes when selecting attributes for filtering.
202
- # @return [void]
203
- def filter_virtual_attributes_that_have_changed
204
- @filter_virtual_attributes_that_changed = true
205
- end
206
- alias_method :filter_virtual_attributes_that_changed, :filter_virtual_attributes_that_have_changed
207
- alias_method :filter_changed_virtual_attributes, :filter_virtual_attributes_that_have_changed
208
- alias_method :virtual_attributes_are_tracked, :filter_virtual_attributes_that_have_changed
209
-
210
- # Gets the internal flag that causes to check virtual attributes
211
- # for changes when selecting attributes for filtering.
212
- # @return [Boolean] +true+ if the virtual attributes should be checked for a change, +false+ otherwise
213
- def filter_virtual_attributes_that_changed?
214
- !!@filter_virtual_attributes_that_changed
215
- end
216
-
217
- private
218
-
219
- def __treat_as_real
220
- @__treat_as_real ||= ActiveModel::AttributeSet.new
221
- end
222
-
223
- end # module ClassMethods
224
-
225
178
  private
226
179
 
227
180
  # Applies operations to elements from set.
228
181
  def operate_on_attrs_from_set(set_name, alter_mode, *args, &block)
229
182
  block_given? or return enum_for(__method__, set_name, alter_mode, *args)
230
- flags = AttributeFiltersHelpers.process_flags(args)
183
+ flags = AFHelpers.process_flags(args)
231
184
  process_all = flags[:process_all]
232
185
  process_blank = flags[:process_blank]
233
186
  no_presence_check = flags[:no_presence_check]
@@ -29,6 +29,9 @@ module ActiveModel
29
29
  # If the argument is a kind of {AttributeSet} then the local set
30
30
  # is taken and wrapped in a {AttributeSet::AttrQuery} instance.
31
31
  #
32
+ # If the argument is +nil+ then the local set is guessed by assuming
33
+ # that the next method in a call chain is really the name of a set.
34
+ #
32
35
  # If the argument is other kind than the specified above then
33
36
  # the method tries to initialize new, local set object and wraps
34
37
  # it in a `AttributeSet::AttrQuery` instance.
@@ -40,23 +43,20 @@ module ActiveModel
40
43
  #
41
44
  # @param set_name [Symbol] name of attribute set, attribute object or any object that can initialize a set
42
45
  # @return [AttributeSet] attribute set
43
- def attribute_set(set_name=nil)
44
- if set_name.nil?
45
- all_attributes
46
- else
47
- ActiveModel::AttributeSet::Query.new(set_name, self)
48
- end
46
+ def attribute_set(set_name = nil)
47
+ ActiveModel::AttributeSet::Query.new(set_name, self)
49
48
  end
50
49
  alias_method :attributes_that_are, :attribute_set
51
50
  alias_method :from_attributes_that, :attribute_set
51
+ alias_method :are_attributes_that, :attribute_set
52
52
  alias_method :are_attributes_that_are, :attribute_set
53
+ alias_method :are_attributes, :attribute_set
54
+ alias_method :are_attributes_for, :attribute_set
53
55
  alias_method :from_attributes_that_are, :attribute_set
54
- alias_method :within_attributes_that_are, :attribute_set
56
+ alias_method :in_attributes_that_are, :attribute_set
55
57
  alias_method :attributes_that, :attribute_set
56
58
  alias_method :attributes_are, :attribute_set
57
59
  alias_method :attributes_for, :attribute_set
58
- alias_method :are_attributes, :attribute_set
59
- alias_method :are_attributes_for, :attribute_set
60
60
  alias_method :attributes_set, :attribute_set
61
61
  alias_method :properties_that, :attribute_set
62
62
 
@@ -71,25 +71,21 @@ module ActiveModel
71
71
  # @param set_name [Symbol] name of attribute set
72
72
  # @return [AttributeSet] attribute set
73
73
  def attribute_set_simple(set_name)
74
- self.class.attribute_set(set_name).dup
74
+ self.class.attribute_set(set_name)
75
75
  end
76
76
 
77
77
  # Returns a set containing all known attributes
78
- # without wrapping it in a proxy.
79
- #
80
- # @return [AttributeSet] attribute set
81
- def all_attributes_simple(no_presence_check = true)
82
- (ActiveModel::AttributeSet.new(attributes.keys) +
83
- all_accessible_attributes(true) +
84
- all_protected_attributes(true) +
85
- __vatrf(no_presence_check)).delete("")
86
- end
87
-
88
- # Returns a set containing all known attributes.
78
+ # without wrapping the result in a proxy.
89
79
  #
80
+ # @param simple [Boolean] optional parameter that disables
81
+ # wrapping a resulting set in a proxy (defaults to +false+)
82
+ # @param no_presence_check [Boolean] optional parameter that
83
+ # disables checking for presence of setters and getters for each
84
+ # virtual and semi-real attribute (defaults to +true+)
90
85
  # @return [AttributeSet] attribute set
91
- def all_attributes
92
- ActiveModel::AttributeSet::Query.new(all_attributes_simple, self)
86
+ def all_attributes(simple = false, no_presence_check = true)
87
+ r = __all_attributes(no_presence_check).deep_dup
88
+ simple ? r : ActiveModel::AttributeSet::Query.new(r, self)
93
89
  end
94
90
  alias_method :all_attributes_set, :all_attributes
95
91
 
@@ -99,8 +95,9 @@ module ActiveModel
99
95
  # wrapping a resulting set in a proxy (defaults to +false+)
100
96
  # @return [AttributeSet] attribute set
101
97
  def all_accessible_attributes(simple = false)
102
- c = self.class.class_eval { respond_to?(:accessible_attributes) ? accessible_attributes : [] }
103
- simple ? AttributeSet.new(c) : AttributeSet::Query.new(c)
98
+ my_class = self.class
99
+ c = my_class.respond_to?(:accessible_attributes) ? my_class.accessible_attributes : []
100
+ simple ? AttributeSet.new(c) : AttributeSet::Query.new(c, self)
104
101
  end
105
102
  alias_method :accessible_attributes_set, :all_accessible_attributes
106
103
 
@@ -110,15 +107,50 @@ module ActiveModel
110
107
  # wrapping a resulting set in a proxy (defaults to +false+)
111
108
  # @return [AttributeSet] attribute set
112
109
  def all_protected_attributes(simple = false)
113
- c = self.class.class_eval { respond_to?(:protected_attributes) ? protected_attributes : [] }
114
- simple ? AttributeSet.new(c) : AttributeSet::Query.new(c)
110
+ my_class = self.class
111
+ c = my_class.respond_to?(:protected_attributes) ? my_class.protected_attributes : []
112
+ simple ? AttributeSet.new(c) : AttributeSet::Query.new(c, self)
115
113
  end
116
114
  alias_method :protected_attributes_set, :all_protected_attributes
117
115
 
116
+ # Returns a set containing attributes declared as virtual with +attr_virtual+.
117
+ #
118
+ # @param simple [Boolean] optional parameter that disables
119
+ # wrapping a resulting set in a proxy (defaults to +false+)
120
+ # @return [AttributeSet] attribute set
121
+ def all_virtual_attributes(simple = false)
122
+ c = self.class.attribute_filters_virtual
123
+ simple ? c : AttributeSet::Query.new(c, self)
124
+ end
125
+ alias_method :virtual_attributes_set, :all_virtual_attributes
126
+ alias_method :attribute_filters_virtual, :all_virtual_attributes
127
+
128
+ # Returns a set containing attributes declared as semi-real.
129
+ #
130
+ # @param simple [Boolean] optional parameter that disables
131
+ # wrapping a resulting set in a proxy (defaults to +false+)
132
+ # @param no_presence_check [Boolean] optional parameter that
133
+ # disables checking for presence of setters and getters for each
134
+ # attribute (defaults to +true+)
135
+ # @return [AttributeSet] attribute set
136
+ def all_semi_real_attributes(simple = false, no_presence_check = true)
137
+ c = self.class.treat_as_real
138
+ c = c.select_accessible(self) unless no_presence_check || c.empty?
139
+ simple ? c : AttributeSet::Query.new(c, self)
140
+ end
141
+ alias_method :semi_real_attributes_set, :all_semi_real_attributes
142
+ alias_method :treat_as_real, :all_semi_real_attributes
143
+
118
144
  # Returns a set containing all attributes that are not accessible attributes.
145
+ #
146
+ # @param simple [Boolean] optional parameter that disables
147
+ # wrapping a resulting set in a proxy (defaults to +false+)
148
+ # @param no_presence_check [Boolean] optional parameter that
149
+ # disables checking for presence of setters and getters for each
150
+ # virtual and semi-real attribute (defaults to +true+)
119
151
  # @return [AttributeSet] attribute set
120
- def all_inaccessible_attributes
121
- all_attributes - all_accessible_attributes(true)
152
+ def all_inaccessible_attributes(simple = false, no_presence_check = true)
153
+ all_attributes(simple, no_presence_check) - all_accessible_attributes(simple)
122
154
  end
123
155
  alias_method :all_non_accessible_attributes, :all_inaccessible_attributes
124
156
  alias_method :inaccessible_attributes_set, :all_inaccessible_attributes
@@ -129,21 +161,31 @@ module ActiveModel
129
161
  # will always return {AttributeSet} object. If there is no such set defined then the returned,
130
162
  # matching set will be empty.
131
163
  #
132
- # @return [Hash{Symbol => AttributeSet<String>}] the collection of attribute sets indexed by their names
164
+ # @return [MetaSet{Symbol => AttributeSet<String>}] the collection of attribute sets indexed by their names
133
165
  def attribute_sets
134
166
  s = self.class.attribute_sets
135
- s.merge!(s) do |set_name, set_object|
136
- ActiveModel::AttributeSet::Query.new(set_object, self)
167
+ s.each_pair do |set_name, set_object|
168
+ s[set_name] = ActiveModel::AttributeSet::Query.new(set_object, self)
137
169
  end
138
- s.default = ActiveModel::AttributeSet::Query.new(ActiveModel::AttributeSet.new.freeze, self)
139
170
  s
140
171
  end
141
172
  alias_method :attributes_sets, :attribute_sets
142
173
  alias_method :properties_sets, :attribute_sets
143
174
 
175
+ # Checks if the given set exists.
176
+ #
177
+ # @param [String, Symbol] set_name name of a set
178
+ # @return [Boolean] +true+ if a set of the given name exists, +false+ otherwise
179
+ def attribute_set_exists?(set_name)
180
+ set_name.present? && self.class.send(:__attribute_sets).key?(set_name.to_sym)
181
+ end
182
+
144
183
  # Returns the set of set names that the attribute of the given
145
184
  # name belongs to.
146
185
  #
186
+ # If the given attribute name is +nil+ then it is taken from
187
+ # the name of a next method in a method call chain.
188
+ #
147
189
  # @note The returned value is a duplicate. Adding or removing
148
190
  # elements to it will have no effect. Altering attribute sets
149
191
  # is possible on a class-level only, since attribute sets
@@ -151,26 +193,64 @@ module ActiveModel
151
193
  #
152
194
  # @param attribute_name [Symbol] name of attribute set
153
195
  # @return [AttributeSet] attribute set
154
- def filtered_attribute(attribute_name)
155
- ActiveModel::AttributeSet::AttrQuery.new(self.class.filter_attribute(attribute_name), self, attribute_name)
196
+ def filtered_attribute(attribute_name = nil)
197
+ ActiveModel::AttributeSet::AttrQuery.new(attribute_name, self)
156
198
  end
157
199
  alias_method :the_attribute, :filtered_attribute
158
200
  alias_method :is_the_attribute, :filtered_attribute
201
+ alias_method :has_the_attribute, :filtered_attribute
159
202
  alias_method :are_attributes, :filtered_attribute
160
203
  alias_method :are_the_attributes, :filtered_attribute
161
204
 
205
+ # Returns the set of set names that the attribute of the given
206
+ # name belongs to.
207
+ #
208
+ # @note The returned value is a duplicate. Adding or removing
209
+ # elements to it will have no effect. Altering attribute sets
210
+ # is possible on a class-level only, since attribute sets
211
+ # are part of models' interfaces.
212
+ #
213
+ # @param attribute_name [Symbol] name of attribute set
214
+ # @return [AttributeSet] attribute set
215
+ def filtered_attribute_simple(attribute_name)
216
+ self.class.filter_attribute(attribute_name)
217
+ end
218
+ alias_method :the_attribute_simple, :filtered_attribute_simple
219
+
162
220
  # Gets all the defined attribute set names hashed by attribute names.
163
221
  #
164
222
  # @note Use +key+ method explicitly to check if the given attribute is assigned to any set. The hash
165
223
  # returned by this method will always return {AttributeSet} object. If the attribute is not assigned
166
224
  # to any set then the returned, matching set will be empty.
167
225
  #
168
- # @return [Hash{String => AttributeSet<Symbol>}] the collection of attribute set names indexed by attribute names
226
+ # @return [MetaSet{String => AttributeSet<Symbol>}] the collection of attribute set names indexed by attribute names
169
227
  def attributes_to_sets
170
228
  self.class.attributes_to_sets
171
229
  end
172
230
  alias_method :attribute_sets_map, :attributes_to_sets
173
231
 
232
+ # Returns a set containing all known attributes
233
+ # without wrapping the result in a proxy.
234
+ #
235
+ # @param no_presence_check [Boolean] optional parameter that
236
+ # disables checking for presence of setters and getters for each
237
+ # virtual and semi-real attribute (defaults to +true+)
238
+ # @return [AttributeSet] attribute set
239
+ def __all_attributes(no_presence_check = true)
240
+ my_class = self.class
241
+ c = my_class.send(:__attribute_filters_semi_real)
242
+ c = c.select_accessible(self) unless no_presence_check || c.empty?
243
+ r = ActiveModel::AttributeSet.new(c)
244
+ r.merge!(my_class.send(:__attribute_filters_virtual))
245
+ r << attributes.keys
246
+ if respond_to?(:accessible_attributes)
247
+ r << accessible_attributes
248
+ r << protected_attributes
249
+ end
250
+ r
251
+ end
252
+ private :__all_attributes
253
+
174
254
  # This module contains class methods
175
255
  # that create DSL for managing attribute sets.
176
256
  module ClassMethods
@@ -207,31 +287,38 @@ module ActiveModel
207
287
  attribute_sets
208
288
  when 1
209
289
  first_arg = args.first
210
- if first_arg.is_a?(Hash) # multiple sets defined
290
+ if first_arg.is_a?(Hash) # [write] multiple sets defined
211
291
  first_arg.each_pair { |k, v| attribute_set(k, v) }
212
292
  nil
213
- else
214
- attribute_sets[first_arg.to_sym]
293
+ else # [read] single set to read
294
+ r = __attribute_sets[first_arg.to_sym]
295
+ r.frozen? ? r : r.deep_dup
215
296
  end
216
297
  else
217
298
  first_arg = args.shift
218
- if first_arg.is_a?(Hash) # multiple sets defined
299
+ if first_arg.is_a?(Hash) # [write] multiple sets defined
219
300
  first_arg.each_pair do |k, v|
220
301
  attribute_set(k, v, args)
221
302
  end
222
- else
223
- AttributeFiltersHelpers.check_wanted_methods(self)
303
+ else # [write core] sinle set and optional attrs given
304
+ AFHelpers.check_wanted_methods(self)
224
305
  add_atrs_to_set(first_arg.to_sym, *args)
225
306
  end
226
307
  nil
227
308
  end
228
309
  end
229
- alias_method :attributes_that_are, :attribute_set
230
- alias_method :attributes_that, :attribute_set
231
- alias_method :attributes_are, :attribute_set
232
- alias_method :attributes_for, :attribute_set
233
- alias_method :attributes_set, :attribute_set
234
- alias_method :properties_that, :attribute_set
310
+ alias_method :attributes_set, :attribute_set
311
+ alias_method :attributes_that_are, :attribute_set
312
+ alias_method :attributes_that, :attribute_set
313
+ alias_method :properties_that, :attribute_set
314
+ alias_method :has_attribute_set, :attribute_set
315
+ alias_method :has_attribute_that, :attribute_set
316
+ alias_method :has_attribute_that_is, :attribute_set
317
+ alias_method :has_attributes, :attribute_set
318
+ alias_method :has_attributes_set, :attribute_set
319
+ alias_method :has_attributes_that_are, :attribute_set
320
+ alias_method :has_attributes_that, :attribute_set
321
+ alias_method :has_properties_that, :attribute_set
235
322
 
236
323
  # @overload filter_attribute()
237
324
  # Gets all the defined attribute sets.
@@ -262,7 +349,7 @@ module ActiveModel
262
349
  # @param set_names [Array<Symbol,String>] names of additional sets to assign attributes to
263
350
  # @return [nil]
264
351
  def filter_attribute(*args)
265
- AttributeFiltersHelpers.check_wanted_methods(self)
352
+ AFHelpers.check_wanted_methods(self)
266
353
  case args.size
267
354
  when 0
268
355
  attributes_to_sets
@@ -272,7 +359,8 @@ module ActiveModel
272
359
  first_arg.each_pair { |k, v| filter_attribute(k, v) }
273
360
  nil
274
361
  else
275
- attributes_to_sets[first_arg.to_s]
362
+ r = __attributes_to_sets_map[first_arg.to_s]
363
+ r.frozen? ? r : r.deep_dup
276
364
  end
277
365
  else
278
366
  first_arg = args.shift
@@ -295,12 +383,17 @@ module ActiveModel
295
383
  nil
296
384
  end
297
385
  end
298
- alias_method :the_attribute, :filter_attribute
299
- alias_method :add_attribute_to_set, :filter_attribute
300
- alias_method :add_attribute_to_sets,:filter_attribute
301
- alias_method :attribute_to_set, :filter_attribute
302
- alias_method :filtered_attribute, :filter_attribute
303
- alias_method :filtered_attributes, :filter_attribute
386
+ alias_method :the_attribute, :filter_attribute
387
+ alias_method :attribute_to_set, :filter_attribute
388
+ alias_method :filtered_attribute, :filter_attribute
389
+ alias_method :filtered_attributes, :filter_attribute
390
+ alias_method :filters_attribute, :filter_attribute
391
+ alias_method :filters_attributes, :filter_attribute
392
+ alias_method :its_attribute, :filter_attribute
393
+ alias_method :has_attribute, :filter_attribute
394
+ alias_method :has_the_attribute, :filter_attribute
395
+ alias_method :has_filtered_attribute, :filter_attribute
396
+ alias_method :has_filtered_attributes, :filter_attribute
304
397
 
305
398
  # Gets all the defined attribute sets.
306
399
  #
@@ -308,13 +401,9 @@ module ActiveModel
308
401
  # will always return {AttributeSet} object. If there is no such set defined then the returned,
309
402
  # matching set will be empty. All set objects are duplicates of the defined sets.
310
403
  #
311
- # @return [Hash{Symbol => AttributeSet<String>}] the collection of attribute sets indexed by their names
404
+ # @return [MetaSet{Symbol => AttributeSet<String>}] the collection of attribute sets indexed by their names
312
405
  def attribute_sets
313
- d = Hash.new(ActiveModel::AttributeSet.new.freeze)
314
- __attribute_sets.each_pair do |set_name, set_object|
315
- d[set_name] = set_object.dup
316
- end
317
- d
406
+ __attribute_sets.deep_dup
318
407
  end
319
408
  alias_method :attributes_sets, :attribute_sets
320
409
  alias_method :properties_sets, :attribute_sets
@@ -323,26 +412,87 @@ module ActiveModel
323
412
  #
324
413
  # @note Use +key+ method explicitly to check if the given attribute is assigned to any set. The hash
325
414
  # returned by this method will always return {AttributeSet} object. If the attribute is not assigned
326
- # to any set then the returned, matching set will be empty.
415
+ # to any set then the returned, matching set will be empty. This method returns a duplicate of each
416
+ # reverse mapped set.
327
417
  #
328
- # @return [Hash{String => AttributeSet<Symbol>}] the collection of attribute set names indexed by attribute names
418
+ # @return [MetaSet{String => AttributeSet<Symbol>}] the collection of attribute set names indexed by attribute names
329
419
  def attributes_to_sets
330
- d = Hash.new(ActiveModel::AttributeSet.new.freeze)
331
- __attributes_to_sets_map.each_pair do |set_name, set_object|
332
- d[set_name] = set_object.dup
333
- end
334
- d
420
+ __attributes_to_sets_map.deep_dup
335
421
  end
336
422
  alias_method :attribute_sets_map, :attributes_to_sets
337
423
 
424
+ # @overload treat_as_real(*attributes)
425
+ # Informs Attribute Filters that the given attributes
426
+ # should be treated as present, even they are not in
427
+ # attributes hash provided by ORM or ActiveModel.
428
+ # Useful when operating on semi-virtual attributes.
429
+ #
430
+ # @note To operate on virtual attributes use +attr_virtual+ instead.
431
+ #
432
+ # @param attributes [Array] list of attribute names
433
+ # @return [void]
434
+ #
435
+ # @overload treat_as_real()
436
+ # Gets the memorized attribute names that should be
437
+ # treated as existing.
438
+ #
439
+ # @return [AttributeSet] set of attribute names
440
+ def treat_as_real(*args)
441
+ return __attribute_filters_semi_real.deep_dup if args.blank?
442
+ __attribute_filters_semi_real << args.flatten.compact.map { |atr| atr.to_s }
443
+ nil
444
+ end
445
+ alias_method :attribute_filters_semi_real, :treat_as_real
446
+ alias_method :treat_attribute_as_real, :treat_as_real
447
+ alias_method :treat_attributes_as_real, :treat_as_real
448
+ alias_method :treats_attribute_as_real, :treat_as_real
449
+ alias_method :treats_attributes_as_real, :treat_as_real
450
+ alias_method :treats_as_real, :treat_as_real
451
+
452
+ # @overload attribute_filters_virtual(*attributes)
453
+ # Informs Attribute Filters that the given attributes
454
+ # should be treated as virtual (even not present in the
455
+ # attributes hash provided by ORM or ActiveModel).
456
+ #
457
+ # @note This method may be used directly if your setter
458
+ # notifies Rails about changes. Otherwise it's recommended
459
+ # to use the DSL keyword +attr_virtual+.
460
+ # @param attributes [Array] list of attribute names
461
+ # @return [void]
462
+ #
463
+ # @overload attribute_filters_virtual()
464
+ # Gets the memorized attribute names that should be
465
+ # treated as virtual.
466
+ #
467
+ # @return [AttributeSet] set of attribute names
468
+ def treat_as_virtual(*args)
469
+ return __attribute_filters_virtual.deep_dup if args.blank?
470
+ __attribute_filters_virtual << args.flatten.compact.map { |atr| atr.to_s }
471
+ nil
472
+ end
473
+ alias_method :attribute_filters_virtual, :treat_as_virtual
474
+ alias_method :treat_attribute_as_virtual, :treat_as_virtual
475
+ alias_method :treat_attributes_as_virtual, :treat_as_virtual
476
+ alias_method :treats_attribute_as_virtual, :treat_as_virtual
477
+ alias_method :treats_attributes_as_virtual, :treat_as_virtual
478
+ alias_method :treats_as_virtual, :treat_as_virtual
479
+
338
480
  private
339
481
 
482
+ def __attribute_filters_semi_real
483
+ @__attribute_filters_semi_real ||= ActiveModel::AttributeSet.new
484
+ end
485
+
486
+ def __attribute_filters_virtual
487
+ @__attribute_filters_virtual ||= ActiveModel::AttributeSet.new
488
+ end
489
+
340
490
  def __attributes_to_sets_map
341
- @__attributes_to_sets_map ||= Hash.new
491
+ @__attributes_to_sets_map ||= MetaSet.new(ActiveModel::MetaSet.new.freeze)
342
492
  end
343
493
 
344
494
  def __attribute_sets
345
- @__attribute_sets ||= Hash.new
495
+ @__attribute_sets ||= MetaSet.new(ActiveModel::AttributeSet.new.freeze)
346
496
  end
347
497
 
348
498
  def add_atrs_to_set(set_name, *atrs)
@@ -351,29 +501,29 @@ module ActiveModel
351
501
  if atr_name.is_a?(Hash) # annotation
352
502
  atr_name.each_pair do |atr_name_b, a_defs|
353
503
  add_atrs_to_set(set_name, atr_name_b)
354
- s = __attribute_sets[set_name] and a_defs.nil? or a_defs.each_pair { |n, v| s.annotate(atr_name_b, n, v) }
504
+ if __attribute_sets.key?(set_name) && a_defs.is_a?(Hash)
505
+ a_defs.each_pair do |n, v|
506
+ __attribute_sets[set_name].annotate(atr_name_b, n, v)
507
+ end
508
+ end
355
509
  end
356
- return
510
+ return nil
357
511
  else
358
512
  atr_name = atr_name.to_s
359
- __attributes_to_sets_map[atr_name] ||= ActiveModel::AttributeSet.new
360
- __attributes_to_sets_map[atr_name] << set_name
513
+ unless __attributes_to_sets_map.key?(atr_name)
514
+ __attributes_to_sets_map[atr_name] = ActiveModel::MetaSet.new
515
+ end
516
+ __attributes_to_sets_map[atr_name] << set_name
361
517
  end
362
518
  end
363
- __attribute_sets[set_name] ||= ActiveModel::AttributeSet.new
364
- __attribute_sets[set_name] << atrs.map{ |a| a.to_s }.freeze
519
+ sanitized_atrs = atrs.map{ |a| a.to_s.dup }
520
+ unless __attribute_sets.key?(set_name)
521
+ __attribute_sets[set_name] = ActiveModel::AttributeSet.new(sanitized_atrs)
522
+ else
523
+ __attribute_sets[set_name] << sanitized_atrs
524
+ end
525
+ nil
365
526
  end
366
527
  end # module ClassMethods
367
-
368
- private
369
-
370
- # Helper that collects untracked virtual attributes that
371
- # have setters and getters.
372
- def __vatrf(no_presence_check = false)
373
- tar = self.class.send(:__treat_as_real)
374
- return tar if no_presence_check || tar.empty?
375
- tar.select { |a| respond_to?(a) && respond_to?("#{a}=") }
376
- end
377
-
378
528
  end # module AttributeMethods
379
529
  end # module ActiveModel