attribute-filters 1.4.0 → 2.0.0

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