attribute-filters 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,12 +2,15 @@
2
2
 
3
3
  require 'attribute-filters'
4
4
  require 'attribute-filters/version'
5
+ require 'attribute-filters/attribute_set_enum'
5
6
  require 'attribute-filters/attribute_set'
6
7
  require 'attribute-filters/attribute_set_query'
8
+ require 'attribute-filters/attribute_set_attrquery'
7
9
 
8
10
  require 'attribute-filters/helpers'
9
11
  require 'attribute-filters/dsl_sets'
10
12
  require 'attribute-filters/dsl_filters'
13
+ require 'attribute-filters/common_filters'
11
14
 
12
15
  if defined? ::Rails
13
16
  require 'attribute-filters/railtie'
@@ -8,6 +8,7 @@
8
8
 
9
9
  require 'active_model'
10
10
 
11
+ # @abstract This namespace is shared with ActveModel.
11
12
  module ActiveModel
12
13
 
13
14
  if defined?(AttributeMethods)
@@ -30,7 +31,7 @@ module ActiveModel
30
31
  singleton_class.send(:alias_method, :included, :included_with_attribute_methods)
31
32
  #singleton_class.send(:alias_method_chain, :included, :attribute_methods)
32
33
  end
33
-
34
+
34
35
  end # ActiveModel::AttributeMethods.class_eval
35
36
 
36
37
  end # if defined?(AttributeMethods)
@@ -12,7 +12,27 @@ require 'set'
12
12
  # @abstract This namespace is shared with ActveModel.
13
13
  module ActiveModel
14
14
  # This class is a data structure used to store
15
- # sets of attributes.
15
+ # set of attributes.
16
16
  class AttributeSet < ::Set
17
+ include AttributeSetEnumerable
18
+ # Adds the given object to the set and returns self.
19
+ # If the object is already in the set, returns nil.
20
+ # If the object is an array it adds each element of the array.
21
+ # The array is not flattened so if it contains other arrays
22
+ # then they will be added as the arrays.
23
+ # When adding an array the returning value is also an array,
24
+ # which contains elements that were successfuly added to set
25
+ # and didn't existed there before.
26
+ #
27
+ # @param o [Object,Array] object to be added to set or array of objects
28
+ # @return [AttributeSet,nil]
29
+ def add(o)
30
+ if o.is_a?(Array)
31
+ o.map{ |e| super(e) }.compact
32
+ else
33
+ super
34
+ end
35
+ end
36
+ alias_method :<<, :add
17
37
  end
18
38
  end
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Paweł Wilk (mailto:pw@gnu.org)
4
+ # Copyright:: (c) 2012 by Paweł Wilk
5
+ # License:: This program is licensed under the terms of {file:LGPL-LICENSE GNU Lesser General Public License} or {file:COPYING Ruby License}.
6
+ #
7
+ # This file contains ActiveModel::AttributeSet::AttrQuery class
8
+ # used to interact with attribute sets containing set names.
9
+
10
+ # @abstract This namespace is shared with ActveModel.
11
+ module ActiveModel
12
+ class AttributeSet
13
+ # This class contains proxy methods used to interact with
14
+ # AttributeSet instances. It's responsible for all of the DSL magic
15
+ # that allows sweet constructs like:
16
+ # the_attribute(:x).is.in_set?
17
+ class AttrQuery < Query
18
+ # This is a proxy method that causes some calls to be
19
+ # intercepted. Is allows to create semi-natural
20
+ # syntax when querying attribute sets containing set names.
21
+ #
22
+ # When the called method name ends with question mark then
23
+ # its name is considered to be an attribute set name that
24
+ # should be tested for presence of the attribute. To use
25
+ # that syntax you have to be sure that there is no already
26
+ # defined method for AttributeSet object which name ends
27
+ # with question mark. Otherwise you may get false positives
28
+ # or a strange errors when trying to test if attribute belongs
29
+ # to a set. The real method call will override your check.
30
+ #
31
+ # @example
32
+ # the_attribute(:some_attribute).is.in?(:some_set)
33
+ # the_attribute(:some_attribute).list.sets
34
+ # the_attribute(:some_attribute).is.in.a.set.that?(:should_be_downcased)
35
+ # the_attribute(:some_attribute).should_be_downcased?
36
+ #
37
+ # @param method_sym [Symbol,String] name of method that will be queued or called on a set
38
+ # @param args [Array] optional arguments to be passed to a method call
39
+ # @yield optional block to be passed to a method call
40
+ def method_missing(method_sym, *args, &block)
41
+ case method_sym.to_sym
42
+ when :are, :is, :one, :is_one, :in, :list, :be, :should,
43
+ :the, :a, :sets, :in_sets, :set, :in_a_set, :in_set, :belongs_to
44
+ self
45
+ when :belongs_to?, :in?, :in_set?, :in_a_set?, :in_the_set?,
46
+ :the_set?, :set?, :is_one_that?, :one_that?, :that?
47
+ if args.present? && args.is_a?(::Array)
48
+ args = args.map{ |a| a.to_sym if a.respond_to?(:to_sym) }
49
+ end
50
+ @set_object.include?(*args, &block)
51
+ else
52
+ set_name_str = method_sym.to_s.dup
53
+ if !@set_object.respond_to?(method_sym) && set_name_str.slice!(/\?\z/) == '?'
54
+ @set_object.include?(set_name_str.to_sym, &block)
55
+ else
56
+ @set_object.method(method_sym).call(*args, &block)
57
+ end
58
+ end
59
+ end
60
+
61
+ # @private
62
+ def respond_to?(name)
63
+ case name.to_sym
64
+ when :are, :is, :one, :is_one, :in, :list, :be, :should, :the, :a, :sets, :in_sets,
65
+ :set, :in_a_set, :in_set, :in?, :in_set?, :in_a_set?, :in_the_set?, :the_set?, :set?,
66
+ :is_one_that?, :one_that?, :that?, :belongs_to?, :belongs_to
67
+ true
68
+ else
69
+ @set_object.respond_to?(name) || name.to_s.slice(-1,1) == '?'
70
+ end
71
+ end
72
+ end # class AttrQuery
73
+ end # class AttributeSet
74
+ end # module ActiveModel
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Paweł Wilk (mailto:pw@gnu.org)
4
+ # Copyright:: (c) 2012 by Paweł Wilk
5
+ # License:: This program is licensed under the terms of {file:LGPL-LICENSE GNU Lesser General Public License} or {file:COPYING Ruby License}.
6
+ #
7
+ # This file contains AttributeSetEnumerable module and AttributeSetEnumerator class.
8
+
9
+ # This module adds some enumerable properties to AttributeSet objects.
10
+ module AttributeSetEnumerable
11
+ # @private
12
+ def select
13
+ if block_given?
14
+ ActiveModel::AttributeSet.new.tap do |r|
15
+ each { |e| r << e if yield(e) }
16
+ end
17
+ else
18
+ AttributeSetEnumerator.new(self, :select)
19
+ end
20
+ end
21
+
22
+ # @private
23
+ def reject
24
+ if block_given?
25
+ ActiveModel::AttributeSet.new.tap do |r|
26
+ each { |e| r << e unless yield(e) }
27
+ end
28
+ else
29
+ AttributeSetEnumerator.new(self, :reject)
30
+ end
31
+ end
32
+
33
+ # @private
34
+ def collect
35
+ if block_given?
36
+ ActiveModel::AttributeSet.new.tap do |r|
37
+ each { |e| r << yield(e) }
38
+ end
39
+ else
40
+ AttributeSetEnumerator.new(self, :map)
41
+ end
42
+ end
43
+ alias_method :map, :collect
44
+
45
+ # @private
46
+ def sort
47
+ ActiveModel::AttributeSet.new(super)
48
+ end
49
+
50
+ # @private
51
+ def sort_by
52
+ ActiveModel::AttributeSet.new(super)
53
+ end
54
+ end
55
+
56
+ # This class adds enumerator for AttributeSet elements.
57
+ class AttributeSetEnumerator < ::Enumerator
58
+ include AttributeSetEnumerable
59
+ end
60
+
@@ -7,6 +7,7 @@
7
7
  # This file contains ActiveModel::AttributeSet::Query class
8
8
  # used to interact with attribute sets.
9
9
 
10
+ # @abstract This namespace is shared with ActveModel.
10
11
  module ActiveModel
11
12
  class AttributeSet
12
13
  # This class contains proxy methods used to interact with
@@ -16,15 +17,15 @@ module ActiveModel
16
17
  class Query < BasicObject
17
18
  # Creates new query object.
18
19
  #
19
- # @param attribute_set_object [AttributeSet] attribute set for which the query will be made
20
+ # @param set_object [AttributeSet] attribute set for which the query will be made
20
21
  # @param am_object [Object] model object which has access to attributes (may be an instance of ActiveRecord or similar)
21
- def initialize(attribute_set_object, am_object)
22
- @attribute_set = attribute_set_object
22
+ def initialize(set_object, am_object)
23
+ @set_object = set_object
23
24
  @am_object = am_object
24
25
  @next_method = nil
25
26
  end
26
27
 
27
- # This is proxy method that causes some calls to be
28
+ # This is a proxy method that causes some calls to be
28
29
  # queued to for the next call. Is allows to create semi-natural
29
30
  # syntax when querying attribute sets.
30
31
  #
@@ -49,26 +50,41 @@ module ActiveModel
49
50
  # @param method_sym [Symbol,String] name of method that will be queued or called on attribute set
50
51
  # @param args [Array] optional arguments to be passed to a method call or to a queued method call
51
52
  # @yield optional block to be passed to a method call or to a queued method call
53
+ # @return [Object] the returned value is passed back from called method
52
54
  def method_missing(method_sym, *args, &block)
53
55
  case method_sym.to_sym
54
56
  when :are, :is, :be, :should
55
57
  return self
56
58
  end
57
- if @method_to_call.nil?
59
+ if @next_method.nil?
58
60
  case method_sym.to_sym
59
- when :all, :any
60
- ::ActiveModel::AttributeSet::Query.new(@attribute_set, @am_object). # new obj. == thread-safe
61
+ when :all, :any, :none, :one
62
+ ::ActiveModel::AttributeSet::Query.new(@set_object, @am_object). # new obj. == thread-safe
61
63
  next_step(method_sym.to_s << "?", args, block)
62
64
  when :list, :show
63
- ::ActiveModel::AttributeSet::Query.new(@attribute_set, @am_object).
65
+ ::ActiveModel::AttributeSet::Query.new(@set_object, @am_object).
64
66
  next_step(:select, args, block)
65
67
  else
66
- @attribute_set.method(method_sym).call(*args, &block)
68
+ r = @set_object.method(method_sym).call(*args, &block)
69
+ return r if r.respond_to?(:__in_as_proxy) || !r.is_a?(::ActiveModel::AttributeSet)
70
+ ::ActiveModel::AttributeSet::Query.new(r, @am_object)
67
71
  end
68
72
  else
69
- method_sym, args, block = @next_method
73
+ m, args, block = @next_method
70
74
  @next_method = nil
71
- @attribute_set.method(m).call { |a| @am_object[a].method(method_sym).call(*args, &block) }
75
+ r = @set_object.method(m).call { |a| @am_object[a].method(method_sym).call(*args, &block) }
76
+ return r if r.respond_to?(:__in_as_proxy) || !r.is_a?(::ActiveModel::AttributeSet)
77
+ ::ActiveModel::AttributeSet::Query.new(r, @am_object)
78
+ end
79
+ end
80
+
81
+ # @private
82
+ def respond_to?(name)
83
+ case name.to_sym
84
+ when :are, :is, :be, :should, :all, :any, :none, :one, :list, :show, :__in_as_proxy
85
+ true
86
+ else
87
+ @set_object.respond_to?(name)
72
88
  end
73
89
  end
74
90
 
@@ -1,4 +1,79 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Paweł Wilk (mailto:pw@gnu.org)
4
+ # Copyright:: (c) 2012 by Paweł Wilk
5
+ # License:: This program is licensed under the terms of {file:LGPL-LICENSE GNU Lesser General Public License} or {file:COPYING Ruby License}.
6
+ #
7
+ # This file contains ActiveModel::AttributeFilters::Common module
8
+ # containing ready-to-use, common filtering methods.
1
9
 
10
+ # @abstract This namespace is shared with ActveModel.
11
+ module ActiveModel
12
+ module AttributeFilters
13
+ # This module contains common, ready-to-use filtering methods.
14
+ module Common
15
+ # Strips attributes from leading and trailing spaces.
16
+ #
17
+ # The attrubutes to be stripped are taken from the attribute set called
18
+ # +should_be_stripped+. It operates directly on attribute's contents.
19
+ #
20
+ # @return [void]
21
+ def strip_attributes
22
+ call_attrs_from_set(:should_be_stripped) { |atr| atr.strip! }
23
+ end
2
24
 
3
- # todo: manually included module with common filters
25
+ # Downcases attributes.
26
+ #
27
+ # The attrubutes to be downcased are taken from the attribute set
28
+ # called +should_be_downcased+. This method is safe to be
29
+ # used with multibyte strings (containing diacritics).
30
+ #
31
+ # @return [void]
32
+ def downcase_attributes
33
+ filter_attrs_from_set(:should_be_downcased) do |atr|
34
+ atr.mb_chars.downcase.to_s
35
+ end
36
+ end
4
37
 
38
+ # Upcases attributes.
39
+ #
40
+ # The attrubutes to be upcased are taken from the attribute set
41
+ # called +should_be_upcased+. This method is safe to be
42
+ # used with multibyte strings (containing diacritics).
43
+ #
44
+ # @return [void]
45
+ def upcase_attributes
46
+ filter_attrs_from_set(:should_be_upcased) do |atr|
47
+ atr.mb_chars.upcase.to_s
48
+ end
49
+ end
50
+
51
+ # Capitalize attributes.
52
+ #
53
+ # The attrubutes to be capitalized are taken from the attribute set
54
+ # called +should_be_capitalized+. This method is safe to be
55
+ # used with multibyte strings (containing diacritics).
56
+ #
57
+ # @return [void]
58
+ def capitalize_attributes
59
+ filter_attrs_from_set(:should_be_capitalized) do |atr|
60
+ atr.mb_chars.capitalize.to_s
61
+ end
62
+ end
63
+
64
+ # Fully capitalize attributes (capitalize each word).
65
+ #
66
+ # The attrubutes to be fully capitalized are taken from the attribute set
67
+ # called +should_be_fully_capitalized+. This method is safe to be
68
+ # used with multibyte strings (containing diacritics).
69
+ #
70
+ # @return [void]
71
+ def fully_capitalize_attributes
72
+ filter_attrs_from_set(:should_be_fully_capitalized) do |atr|
73
+ atr.mb_chars.split(' ').map { |n| n.capitalize }.join(' ')
74
+ end
75
+ end
76
+
77
+ end # module Common
78
+ end # module AttributeFilters
79
+ end # module ActiveModel
@@ -6,6 +6,7 @@
6
6
  #
7
7
  # This file contains modules with methods that create DSL for managing attribute filters.
8
8
 
9
+ # @abstract This namespace is shared with ActveModel.
9
10
  module ActiveModel
10
11
  module AttributeFilters
11
12
  # @private
@@ -20,8 +21,6 @@ module ActiveModel
20
21
  # to the given attribute set.
21
22
  #
22
23
  # @param set_name [AttributeSet] set of attributes used to get attributes
23
- # @param alter_mode [Boolean] if set then the existence
24
- # of attribute is checked by testing if writer is defined; otherwise the reader is checked
25
24
  # @param process_all [Boolean] if set then all the attributes from the attribute set are selected,
26
25
  # not just attributes that has changed
27
26
  # @param no_presence_check [Boolean] if set then the checking whether attribute exists will be
@@ -30,9 +29,13 @@ module ActiveModel
30
29
  def attributes_to_filter(set_name, process_all = false, no_presence_check = false)
31
30
  atf = attribute_set(set_name)
32
31
  if process_all
33
- no_presence_check ? atf : atf & attributes.keys
32
+ no_presence_check ? atf : atf & (__vatrf(no_presence_check) + attributes.keys)
34
33
  else
35
- atf & changed_attributes.keys
34
+ if self.class.filter_virtual_attributes_that_changed?
35
+ atf & changes.keys
36
+ else
37
+ atf & (__vatrf(no_presence_check) + changes.keys)
38
+ end
36
39
  end
37
40
  end
38
41
 
@@ -46,7 +49,7 @@ module ActiveModel
46
49
  # It's major purpose is to create filtering methods.
47
50
  #
48
51
  # Only the
49
- # {http://rubydoc.info/gems/activemodel/ActiveModel/Dirty#changed_attributes-instance_method changed attributes}
52
+ # {http://rubydoc.info/gems/activemodel/ActiveModel/Dirty#changes-instance_method changed attributes/properties}
50
53
  # are selected, unless the +process_all+ flag is
51
54
  # given. If that flag is given then presence of each attribute is verified,
52
55
  # unless the +no_presence_check+ flag is also set. Attributes with empty or unset values
@@ -108,7 +111,7 @@ module ActiveModel
108
111
  # It's major purpose is to iterate through attributes and/or work directly with their values.
109
112
  #
110
113
  # Only the
111
- # {http://rubydoc.info/gems/activemodel/ActiveModel/Dirty#changed_attributes-instance_method changed attributes}
114
+ # {http://rubydoc.info/gems/activemodel/ActiveModel/Dirty#changes-instance_method changed attributes/properties}
112
115
  # are selected, unless the +process_all+ flag is
113
116
  # given. If that flag is given then presence of each attribute is verified,
114
117
  # unless the +no_presence_check+ flag is also set. Attributes with
@@ -157,8 +160,56 @@ module ActiveModel
157
160
  alias_method :for_attributes_that_are, :for_each_attr_from_set
158
161
  alias_method :for_attributes_which_are, :for_each_attr_from_set
159
162
 
163
+ module ClassMethods
164
+ # @overload treat_as_real(*attributes)
165
+ # Informs Attribute Filters that the given attributes
166
+ # should be treated as present, even they are not in
167
+ # attributes hash provided by ORM or ActiveModel.
168
+ # Useful when operating on virtual attributes.
169
+ #
170
+ # @param attributes [Array] list of attribute names
171
+ # @return [void]
172
+ #
173
+ # @overload treat_as_real()
174
+ # Gets the memorized attribute names that should be
175
+ # treated as existing.
176
+ #
177
+ # @return [AttributeSet] set of attribute name
178
+ def treat_as_real(*args)
179
+ return __treat_as_real.dup if args.blank?
180
+ __treat_as_real << args.flatten.compact.map { |atr| atr.to_s }
181
+ nil
182
+ end
183
+ alias_method :treat_attribute_as_real, :treat_as_real
184
+ alias_method :treat_attributes_as_real, :treat_as_real
185
+
186
+ # Sets the internal flag that causes to check virtual attributes
187
+ # for changes when selecting attributes for filtering.
188
+ # @return [void]
189
+ def filter_virtual_attributes_that_have_changed
190
+ @filter_virtual_attributes_that_changed = true
191
+ end
192
+ alias_method :filter_virtual_attributes_that_changed, :filter_virtual_attributes_that_have_changed
193
+ alias_method :filter_changed_virtual_attributes, :filter_virtual_attributes_that_have_changed
194
+
195
+ # Gets the internal flag that causes to check virtual attributes
196
+ # for changes when selecting attributes for filtering.
197
+ # @return [Boolean] +true+ if the virtual attributes should be checked for a change, +false+ otherwise
198
+ def filter_virtual_attributes_that_changed?
199
+ !!@filter_virtual_attributes_that_changed
200
+ end
201
+
202
+ private
203
+
204
+ def __treat_as_real
205
+ @__treat_as_real ||= ActiveModel::AttributeSet.new
206
+ end
207
+
208
+ end # module ClassMethods
209
+
160
210
  private
161
211
 
212
+ # Applies operations to elements from set.
162
213
  def operate_on_attrs_from_set(set_name, alter_mode, *args, &block)
163
214
  flags = AttributeFiltersHelpers.process_flags(args)
164
215
  process_all = flags[:process_all]
@@ -194,5 +245,15 @@ module ActiveModel
194
245
  end
195
246
  end
196
247
 
248
+ private
249
+
250
+ # Helper that collects virtual attributes that
251
+ # have setters and getters.
252
+ def __vatrf(no_presence_check = false)
253
+ tar = self.class.send(:__treat_as_real)
254
+ return tar if no_presence_check || tar.empty?
255
+ tar.select { |a| respond_to?(a) && respond_to?("#{a}=") }
256
+ end
257
+
197
258
  end # module AttributeFilters
198
259
  end # module ActiveModel