attribute-filters 2.0.0 → 2.0.1

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.
data.tar.gz.sig CHANGED
Binary file
data/ChangeLog CHANGED
@@ -1,3 +1,54 @@
1
+ commit 364a5df0dfe8bed367351c55b7b64c174b2a3d91
2
+ Author: Paweł Wilk <siefca@gnu.org>
3
+ Date: Sun Nov 4 16:44:10 2012 +0100
4
+
5
+ Version 2.0.1
6
+
7
+ commit ae4f36ca7f011045898f0d21a2a7c4fad3064446
8
+ Author: Paweł Wilk <siefca@gnu.org>
9
+ Date: Sun Nov 4 16:17:03 2012 +0100
10
+
11
+ Tracking changes of virtual attributes improved; wrapping method is now waiting for a getter method
12
+
13
+ commit de71213689e9246e8694f65fc26b4dfb84e9e940
14
+ Author: Paweł Wilk <siefca@gnu.org>
15
+ Date: Sun Nov 4 16:16:10 2012 +0100
16
+
17
+ Added AttributeSet#to_hash and AttributeSet#to_attribute_set
18
+
19
+ commit 732f6a0546bd2420f676d52dcc6be54fc190ea61
20
+ Author: Paweł Wilk <siefca@gnu.org>
21
+ Date: Sun Nov 4 03:26:57 2012 +0100
22
+
23
+ Virtual attributes tracking improved; now the placement of attr_virtual keyword does not matter
24
+
25
+ commit d7c3b087fd0cdb66ad7a42e30ae55d2d896ff505
26
+ Author: Paweł Wilk <siefca@gnu.org>
27
+ Date: Sun Nov 4 03:25:42 2012 +0100
28
+
29
+ Created abstraction layer that allows to keep all needed data in two class instance variables:
30
+
31
+ - @attribute_sets (for sets, attributes map, virtual and semi-real items),
32
+ - @attribute_filters (for filtering methods).
33
+
34
+ commit f1e63bc24ceb941e84e354f3f28ead899eaacc77
35
+ Author: Paweł Wilk <siefca@gnu.org>
36
+ Date: Fri Nov 2 08:43:48 2012 +0100
37
+
38
+ MetaSet improved, removed grandparent.class.bind(self).call
39
+
40
+ commit b5a915f50d823d39657a91b1513e0ba4bd690490
41
+ Author: Paweł Wilk <siefca@gnu.org>
42
+ Date: Thu Nov 1 20:57:25 2012 +0100
43
+
44
+ Description updated
45
+
46
+ commit bda2e1073095712fe1aece55dbf40ac5fb4f4bc9
47
+ Author: Paweł Wilk <siefca@gnu.org>
48
+ Date: Thu Nov 1 20:48:24 2012 +0100
49
+
50
+ Gem description updated
51
+
1
52
  commit 9d5082a0f3d5b9349b1792a420f273b9898f1a9e
2
53
  Author: Paweł Wilk <siefca@gnu.org>
3
54
  Date: Thu Nov 1 17:07:24 2012 +0100
data/README.md CHANGED
@@ -16,6 +16,11 @@ and some syntactic sugar to Rails, thereby allowing you
16
16
  to express filtering and grouping model attributes
17
17
  in a concise and clean way.
18
18
 
19
+ If you are a fan of declarative style, this gem is for you.
20
+ It lets you program your models in a way that it's clear
21
+ to see what's going on with attributes just by looking
22
+ at the top of their classes.
23
+
19
24
  When?
20
25
  -----
21
26
 
@@ -152,6 +157,20 @@ class User < ActiveRecord::Base
152
157
  end
153
158
  ```
154
159
 
160
+ Tracking changes and filtering virtual attributes is also easy:
161
+
162
+ ```ruby
163
+ class User < ActiveRecord::Base
164
+ include ActiveModel::AttributeFilters::Split
165
+
166
+ split_attribute :real_name => [ :first_name, :last_name ]
167
+ before_validation :filter_attributes
168
+
169
+ attr_virtual :real_name
170
+ attr_accessor :real_name
171
+ end
172
+ ```
173
+
155
174
  Usage and more examples
156
175
  -----------------------
157
176
 
@@ -2,12 +2,12 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "attribute-filters"
5
- s.version = "2.0.0.20121101164113"
5
+ s.version = "2.0.1.20121104164328"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Pawe\u{142} Wilk"]
9
9
  s.cert_chain = ["/Users/siefca/.gem/gem-public_cert.pem"]
10
- s.date = "2012-11-01"
10
+ s.date = "2012-11-04"
11
11
  s.description = "Concise way of filtering model attributes in Rails."
12
12
  s.email = ["pw@gnu.org"]
13
13
  s.extra_rdoc_files = ["Manifest.txt"]
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.rubyforge_project = "attribute-filters"
19
19
  s.rubygems_version = "1.8.24"
20
20
  s.signing_key = "/Users/siefca/.gem/gem-private_key.pem"
21
- s.summary = "Concise way of filtering model attributes in Rails"
21
+ s.summary = "Concise way of filtering and grouping model attributes in Rails"
22
22
 
23
23
  if s.respond_to? :specification_version then
24
24
  s.specification_version = 3
data/docs/HISTORY CHANGED
@@ -1,11 +1,25 @@
1
+ === 2.0.1 / 2012-10-04
2
+
3
+ * major enhancements
4
+
5
+ * Virtual attributes changes tracking improved; the `attr_virtual` DSL keyword can be placed anywhere
6
+ * Internal sets refactored, added abstraction layer that utilizes just two class instance variables:
7
+ * `@attribute_sets` (for keeping sets, attributes map, virtual and semi-real items)
8
+ * `@attribute_filters` (for keeping filtering methods)
9
+
10
+ * minor enhancements
11
+
12
+ * Added conversion methods: AttributeSet#to_hash, AttributeSet#to_attribute_set
13
+ * `MetaSet` improved, removed grandparent.class.bind(self).call
14
+
1
15
  === 2.0.0 / 2012-10-01
2
16
 
3
17
  * major enhancements
4
18
 
5
19
  * Operating speed increased by 100%
6
20
  * API change:
7
- * AttributeSet is now based on a Hash
8
- * Added MetaSet for keeping sets of attribute sets
21
+ * `AttributeSet` is now based on a Hash
22
+ * Added `MetaSet` for keeping sets of attribute sets
9
23
  * Added DSL keyword for registering filtering methods
10
24
  * DSL method `filter_attributes` is using filtering methods registered before
11
25
  * Creation of duplicates for sets moved to class-level accessors
data/docs/TODO CHANGED
@@ -1,9 +1,3 @@
1
-
2
- * attr_virtual - wrap method_defined? and add some gotchas into it that will wait for accessor to be added
3
- there already is a hash for attr_virtual, all we need to do is to check any added accessor against that hash
4
- that will only apply when there weren't accessors while using attr_virtual
5
-
6
-
7
1
  * add sanitize to common filters
8
2
  -- sanitize_url
9
3
  -- sanitize_string
data/docs/USAGE.md CHANGED
@@ -508,6 +508,45 @@ Example:
508
508
  # => #<ActiveModel::AttributeSet: {"lol"}>
509
509
  ```
510
510
 
511
+ You can check which virtual attribute is automatically tracked for changes (was added using `attr_virtual`
512
+ and has wrapped setter method), which is still waiting for its setter and getter methods to be created,
513
+ and which was added using `treat_as_virtual` with the assumption that the changes tracking will be done
514
+ manually by the properly constructed setter.
515
+
516
+ Each element of a set containing virtual attributes has a control marker attached to it (instead of annotations) so
517
+ you can read this marker to know what is the status of a certain attribute.
518
+
519
+ To get a hash of attributes and statuses pairs you may use (in an instance method that belongs to a model):
520
+
521
+ ```ruby
522
+ all_virtual_attributes.to_hash
523
+ ```
524
+
525
+ or just print them:
526
+
527
+ ```ruby
528
+ all_virtual_attributes.each_pair{ |k,v| puts "#{k}: #{v}" }
529
+ ```
530
+
531
+ To read the status of a single attribute:
532
+
533
+ ```ruby
534
+ all_virtual_attributes["attribute_name"]
535
+ ```
536
+
537
+ To select the attributes that are meeting certain criteria:
538
+
539
+ ```ruby
540
+ all_virtual_attributes.select{|k,v| v == :tracked}
541
+ end
542
+ ```
543
+
544
+ The statuses are symbols and have the following meanings:
545
+
546
+ * `:no_wrap` – a virtual attribute was added using `treat_as_virtual` and changes tracking should be done manually
547
+ * `:tracked` – a setter method of an attribute was automatically wrapped to track changes
548
+ * `:waiting` – a virtual attribute is to be tracked when its setter method will be defined
549
+
511
550
  Instead of `all_virtual_attributes` you may also use the alias:
512
551
 
513
552
  * `virtual_attributes_set`
@@ -1339,8 +1378,8 @@ your setter and you're done.
1339
1378
  This approach can be easily used with predefined filters. The benefit of it,
1340
1379
  contrary to other methods, is that a filter will never be called redundantly.
1341
1380
 
1342
- You should use the built-in DSL keyword **`attr_virtual`** that will create
1343
- setter and getter for you.
1381
+ You should use the built-in DSL keyword **`attr_virtual`** that will enable changes
1382
+ tracking for your attribute.
1344
1383
 
1345
1384
  ```ruby
1346
1385
  class User < ActiveRecord::Base
@@ -1370,9 +1409,10 @@ end
1370
1409
  Note that for Rails version 3 you may need to declare attribute as accessible using `attr_accessible`
1371
1410
  if you want controllers to be able to update its value through assignment passed to model.
1372
1411
 
1373
- You may change the setter and getter for vitrtual attribute on you own, but it should be done
1374
- somewhere in the code **before** the `attr_virtual` clause. That will allow `attr_virtual`
1375
- to wrap you methods and enable tracking of changes for the attribute.
1412
+ You have to create setter and getter for virtual attributes on you own.
1413
+ It may be done before or after `attr_virtual` keyword.
1414
+
1415
+ Example:
1376
1416
 
1377
1417
  ```ruby
1378
1418
  class User < ActiveRecord::Base
@@ -1393,6 +1433,9 @@ class User < ActiveRecord::Base
1393
1433
  end
1394
1434
  filtering_method :split_attributes, :should_be_splitted
1395
1435
 
1436
+ # inform AF that the model has a virtual attribute
1437
+ has_virtual_attribute :real_name
1438
+
1396
1439
  # own setter
1397
1440
  def real_name=(val)
1398
1441
  # do somehing specific here (or not)
@@ -1405,7 +1448,34 @@ class User < ActiveRecord::Base
1405
1448
  @real_name
1406
1449
  end
1407
1450
 
1408
- attr_virtual :real_name
1451
+ end
1452
+ ```
1453
+
1454
+ Instead of `attr_virtual` you may also use its alias:
1455
+
1456
+ * `has_virtual_attribute`
1457
+
1458
+ There are (rare) cases when you may wish to treat attributes as virtual but without automagically wrapping their setter
1459
+ methods to track changes (e.g. setter is already doing it manually). In such cases **make sure that the setter and getter exist** and use low-level method called `treat_as_virtual`, for instance:
1460
+
1461
+ ```ruby
1462
+ class User < ActiveRecord::Base
1463
+
1464
+ splits_attributes :real_name
1465
+ before_validation :filter_attributes
1466
+
1467
+ # inform AF that the model has a virtual attribute
1468
+ # and changes tracking is already implemented
1469
+ treats_as_virtual :real_name
1470
+
1471
+ # own getter
1472
+ attr_reader :real_name
1473
+
1474
+ # own setter that implements changes tracking
1475
+ def real_name=(val)
1476
+ attribute_will_change!('real_name') if val != real_name
1477
+ @real_name = val
1478
+ end
1409
1479
 
1410
1480
  end
1411
1481
  ```
@@ -29,7 +29,6 @@ module ActiveModel
29
29
  if singleton_class.method_defined?(:included)
30
30
  singleton_class.send(:alias_method, :included_without_attribute_methods, :included)
31
31
  singleton_class.send(:alias_method, :included, :included_with_attribute_methods)
32
- #singleton_class.send(:alias_method_chain, :included, :attribute_methods)
33
32
  end
34
33
 
35
34
  end # ActiveModel::AttributeMethods.class_eval
@@ -7,131 +7,186 @@
7
7
  # This file contains ActiveModel::AttributeSet class
8
8
  # used to interact with attribute sets.
9
9
 
10
+ require 'set'
11
+
10
12
  # @abstract This namespace is shared with ActveModel.
11
13
  module ActiveModel
12
- # This class is a data structure used to store
13
- # set of attributes.
14
- class AttributeSet < Hash
15
- include ActiveModel::AttributeSet::Enumerable
16
-
17
- # Helpers module shortcut
18
- AFHelpers = ActiveModel::AttributeFilters::AttributeFiltersHelpers
14
+ module AttributeFilters
15
+ module AttributeSetMethods
16
+ include ActiveModel::AttributeSet::Enumerable
19
17
 
20
- # Creates a new instance of attribute set.
21
- #
22
- # @return [AttributeSet] an AttributeSet object
23
- def initialize(*args)
24
- return super if args.count == 0
25
- r = super()
26
- add(args)
27
- r
28
- end
18
+ # Helpers module shortcut
19
+ AFHelpers = ActiveModel::AttributeFilters::AttributeFiltersHelpers
29
20
 
30
- # Adds the given object to the set and returns self.
31
- # If the object is already in the set, returns nil.
32
- # If the object is an array it adds each element of the array.
33
- # The array is not flattened so if it contains other arrays
34
- # then they will be added as the arrays.
35
- # When adding an array the returning value is also an array,
36
- # which contains elements that were successfuly added to set
37
- # and didn't existed there before.
38
- #
39
- # @param args [Array<Object,Hash,Array,Enumerable>] object(s) to be added to set
40
- # @return [AttributeSet] current attribute set object
41
- def add(*args)
42
- args.flatten.each do |a|
43
- if a.is_a?(Hash)
44
- deep_merge!(a)
45
- elsif a.is_a?(::Enumerable)
46
- a.each { |e| self[e] = true unless e.blank? }
47
- else
48
- self[a] = true
21
+ # Adds the given object to the set and returns self.
22
+ # If the object is already in the set, returns nil.
23
+ # If the object is an array it adds each element of the array.
24
+ # The array is not flattened so if it contains other arrays
25
+ # then they will be added as the arrays.
26
+ # When adding an array the returning value is also an array,
27
+ # which contains elements that were successfuly added to set
28
+ # and didn't existed there before.
29
+ #
30
+ # @param args [Array<Object,Hash,Array,Enumerable>] object(s) to be added to set
31
+ # @return [AttributeSet] current attribute set object
32
+ def add(*args)
33
+ args.flatten.each do |a|
34
+ if a.is_a?(Hash)
35
+ deep_merge!(a)
36
+ elsif a.is_a?(::Enumerable)
37
+ a.each { |e| self[e] = true unless e.blank? }
38
+ else
39
+ self[a] = true
40
+ end
49
41
  end
42
+ self
50
43
  end
51
- self
52
- end
53
- alias_method :<<, :add
44
+ alias_method :<<, :add
54
45
 
55
- # @private
56
- def to_a
57
- keys
58
- end
46
+ # Returns an array of attribute names.
47
+ #
48
+ # @return [Array<String>]
49
+ def to_a
50
+ keys
51
+ end
59
52
 
60
- # @private
61
- def to_set
62
- keys.to_set
63
- end
53
+ # Returns a hash based on a set.
54
+ #
55
+ # @return [Hash]
56
+ def to_hash
57
+ Hash.new.deep_merge(self)
58
+ end
59
+ alias_method :to_h, :to_hash
64
60
 
65
- # Adds two sets by deeply merging their contents.
66
- # If any value stored in one set under conflicting key
67
- # is +true+, +false+ or +nil+ then value is taken from other set.
68
- # If one of the conflicting values is a kind of Hash and
69
- # the other is not the it's converted to a hash which is merged in.
70
- # Otherwise the left value wins.
71
- #
72
- # @return [AttributeSet] resulting set
73
- def +(o)
74
- my_class = self.class
75
- o = my_class.new(o) unless o.is_a?(Hash)
76
- r = my_class.new
77
- (keys + o.keys).uniq.each do |k|
78
- if self.key?(k) && o.key?(k)
79
- r[k] = merge_set(self[k], o[k]) { |a, b| a + b }
80
- else
81
- r[k] = AFHelpers.safe_dup(self[k] || o[k])
61
+ # Returns self.
62
+ #
63
+ # @return [AttributeSet]
64
+ def to_attribute_set
65
+ self
66
+ end
67
+
68
+ # Returns a set containing attribute names.
69
+ #
70
+ # @return [Set<String>]
71
+ def to_set
72
+ keys.to_set
73
+ end
74
+
75
+ # Adds two sets by deeply merging their contents.
76
+ # If any value stored in one set under conflicting key
77
+ # is +true+, +false+ or +nil+ then value is taken from other set.
78
+ # If one of the conflicting values is a kind of Hash and
79
+ # the other is not the it's converted to a hash which is merged in.
80
+ # Otherwise the left value wins.
81
+ #
82
+ # @return [AttributeSet] resulting set
83
+ def +(o)
84
+ my_class = self.class
85
+ o = my_class.new(o) unless o.is_a?(Hash)
86
+ r = my_class.new
87
+ (keys + o.keys).uniq.each do |k|
88
+ if self.key?(k) && o.key?(k)
89
+ r[k] = merge_set(self[k], o[k]) { |a, b| a + b }
90
+ else
91
+ r[k] = AFHelpers.safe_dup(self[k] || o[k])
92
+ end
82
93
  end
94
+ r
83
95
  end
84
- r
85
- end
86
96
 
87
- # Subtracts the given set from the current one
88
- # by removing all the elements that have the same keys.
89
- #
90
- # @return [AttributeSet] resulting set
91
- def -(o)
92
- o = self.class.new(o) unless o.is_a?(Hash)
93
- reject { |k, v| o.include?(k) }
94
- end
97
+ # Subtracts the given set from the current one
98
+ # by removing all the elements that have the same keys.
99
+ #
100
+ # @return [AttributeSet] resulting set
101
+ def -(o)
102
+ o = self.class.new(o) unless o.is_a?(Hash)
103
+ reject { |k, v| o.include?(k) }
104
+ end
95
105
 
96
- # Returns a new attribute set containing elements
97
- # common to the attribute set and the given enumerable object.
98
- # Annotations from other set that aren't in this set are copied.
99
- #
100
- # @param o [Enumerable] object to intersect with
101
- # @return [AttributeSet] intersection of objects
102
- def &(o)
103
- my_class = self.class
104
- o = my_class.new(o) unless o.is_a?(Hash)
105
- r = my_class.new
106
- each_pair do |k, my_v|
107
- if o.include?(k)
108
- r[k] = merge_set(my_v, o[k]) { |a, b| a & b }
106
+ # Returns a new attribute set containing elements
107
+ # common to the attribute set and the given enumerable object.
108
+ # Annotations from other set that aren't in this set are copied.
109
+ #
110
+ # @param o [Enumerable] object to intersect with
111
+ # @return [AttributeSet] intersection of objects
112
+ def &(o)
113
+ my_class = self.class
114
+ o = my_class.new(o) unless o.is_a?(Hash)
115
+ r = my_class.new
116
+ each_pair do |k, my_v|
117
+ if o.include?(k)
118
+ r[k] = merge_set(my_v, o[k]) { |a, b| a & b }
119
+ end
109
120
  end
121
+ r
110
122
  end
111
- r
112
- end
113
- alias_method :intersection, :&
114
- alias_method :intersect, :&
123
+ alias_method :intersection, :&
124
+ alias_method :intersect, :&
115
125
 
116
- # Returns a new attribute set containing elements
117
- # exclusive between the set and the given enumerable object
118
- # (exclusive disjuction).
119
- #
120
- # @param o [Enumerable] object to exclusively disjunct with
121
- # @return [AttributeSet] resulting set
122
- def ^(o)
123
- my_class = self.class
124
- o = my_class.new(o) unless o.is_a?(Hash)
125
- r = my_class.new
126
- (o.keys + keys).uniq.each do |k|
127
- if key?(k)
128
- next if o.key?(k)
129
- src = self[k]
130
- elsif o.key?(k)
131
- src = o[k]
126
+ # Returns a new attribute set containing elements
127
+ # exclusive between the set and the given enumerable object
128
+ # (exclusive disjuction).
129
+ #
130
+ # @param o [Enumerable] object to exclusively disjunct with
131
+ # @return [AttributeSet] resulting set
132
+ def ^(o)
133
+ my_class = self.class
134
+ o = my_class.new(o) unless o.is_a?(Hash)
135
+ r = my_class.new
136
+ (o.keys + keys).uniq.each do |k|
137
+ if key?(k)
138
+ next if o.key?(k)
139
+ src = self[k]
140
+ elsif o.key?(k)
141
+ src = o[k]
142
+ end
143
+ r[k] = AFHelpers.safe_dup(src)
144
+ end
145
+ r
146
+ end
147
+
148
+ private
149
+
150
+ # Internal method for merging sets.
151
+ def merge_set(my_v, ov, my_class = self.class)
152
+ if my_v.is_a?(Hash)
153
+ if ov.is_a?(Hash)
154
+ my_v = my_class.new(my_v) unless my_v.is_a?(my_class)
155
+ ov = my_class.new(ov) unless ov.is_a?(my_class)
156
+ return yield(my_v, ov)
157
+ else
158
+ a_hash = my_v
159
+ a_other = ov
160
+ end
161
+ else
162
+ if ov.is_a?(Hash)
163
+ a_hash = ov
164
+ a_other = my_v
165
+ else
166
+ return my_v === true ? ov : my_v
167
+ end
168
+ end
169
+ if a_other === true || a_other === false || a_other.nil?
170
+ a_hash
171
+ else
172
+ a_hash.deep_merge(my_class.new(ov))
132
173
  end
133
- r[k] = AFHelpers.safe_dup(src)
134
174
  end
175
+ end # module AttributeSetMethods
176
+ end # module AttributeFilters
177
+
178
+ # This class is a data structure used to store
179
+ # set of attributes.
180
+ class AttributeSet < Hash
181
+ include AttributeFilters::AttributeSetMethods
182
+
183
+ # Creates a new instance of attribute set.
184
+ #
185
+ # @return [AttributeSet] an AttributeSet object
186
+ def initialize(*args)
187
+ return super if args.count == 0
188
+ r = super()
189
+ add(args)
135
190
  r
136
191
  end
137
192
 
@@ -149,52 +204,5 @@ module ActiveModel
149
204
  end
150
205
  end
151
206
 
152
- private
153
-
154
- # Internal method for merging sets.
155
- def merge_set(my_v, ov, my_class = self.class)
156
- if my_v.is_a?(Hash)
157
- if ov.is_a?(Hash)
158
- my_v = my_class.new(my_v) unless my_v.is_a?(my_class)
159
- ov = my_class.new(ov) unless ov.is_a?(my_class)
160
- return yield(my_v, ov)
161
- else
162
- a_hash = my_v
163
- a_other = ov
164
- end
165
- else
166
- if ov.is_a?(Hash)
167
- a_hash = ov
168
- a_other = my_v
169
- else
170
- return my_v === true ? ov : my_v
171
- end
172
- end
173
- if a_other === true || a_other === false || a_other.nil?
174
- a_hash
175
- else
176
- a_hash.deep_merge(my_class.new(ov))
177
- end
178
- end
179
207
  end # class AttributeSet
180
-
181
- # This is a kind of AttributeSet class
182
- # but its purpose it so store other information
183
- # than attribute names.
184
- class MetaSet < AttributeSet
185
- def initialize(*args)
186
- Hash.instance_method(:initialize).bind(self).call(*args)
187
- end
188
- def inspect(*args)
189
- Hash.instance_method(:inspect).bind(self).call(*args)
190
- end
191
- # Internal method for merging sets.
192
- def merge_set(my_v, ov, my_class = self.class)
193
- if my_v.is_a?(my_class) && ov.is_a?(my_class)
194
- my_v.deep_merge(ov)
195
- else
196
- my_v
197
- end
198
- end
199
- end
200
208
  end # module ActiveModel
@@ -22,7 +22,7 @@ module ActiveModel
22
22
  def filtering_method(method_name, set_name)
23
23
  set_name = set_name.to_sym
24
24
  return unless method_defined?(method_name)
25
- f = (@__filtering_sets ||= MetaSet.new)
25
+ f = (@attribute_filters ||= MetaSet.new)
26
26
  f[set_name] = method_name unless f.key?(set_name)
27
27
  nil
28
28
  end
@@ -40,8 +40,8 @@ module ActiveModel
40
40
  def included(base)
41
41
 
42
42
  # merge filtering sets from filtering modules to models
43
- fs = @__filtering_sets
44
- base.class_eval { (@__filtering_sets ||= MetaSet.new).merge!(fs) } unless fs.nil?
43
+ fs = @attribute_filters
44
+ base.class_eval { (@attribute_filters ||= MetaSet.new).merge!(fs) } unless fs.nil?
45
45
 
46
46
  if base.const_defined?(:ClassMethods) &&
47
47
  base.instance_of?(::Module) &&
@@ -52,8 +52,8 @@ module ActiveModel
52
52
  base.class_eval <<-EVAL
53
53
  unless singleton_class.method_defined?(:included)
54
54
  def self.included(base)
55
- fs = @__filtering_sets
56
- base.class_eval { (@__filtering_sets ||= MetaSet.new).merge!(fs) } unless fs.nil?
55
+ fs = @attribute_filters
56
+ base.class_eval { (@attribute_filters ||= MetaSet.new).merge!(fs) } unless fs.nil?
57
57
  base.extend ClassMethods
58
58
  end
59
59
  end
@@ -97,7 +97,7 @@ module ActiveModel
97
97
  #
98
98
  # @return [nil]
99
99
  def filter_attributes
100
- as, fs = *self.class.class_eval { [__attribute_sets, @__filtering_sets] }
100
+ as, fs = *self.class.class_eval { [__attribute_sets, @attribute_filters] }
101
101
  return if fs.blank? || as.blank?
102
102
  as.each_pair { |set_name, o| send(fs[set_name]) if fs.has_key?(set_name) }
103
103
  nil
@@ -107,7 +107,7 @@ module ActiveModel
107
107
  #
108
108
  # @return [MetaSet{Symbol => Symbol}] a meta set of filtering methods and associated sets
109
109
  def filtering_methods
110
- f = self.class.instance_variable_get(:@__filtering_sets)
110
+ f = self.class.instance_variable_get(:@attribute_filters)
111
111
  f.nil? ? ActiveModel::MetaSet.new : f.dup
112
112
  end
113
113
 
@@ -12,8 +12,48 @@ module ActiveModel
12
12
  module AttributeFilters
13
13
  module ClassMethods
14
14
 
15
+ unless method_defined?(:method_added)
16
+ def method_added(x); end
17
+ end
18
+
19
+ # @private
20
+ def method_added_with_afv(method_name)
21
+ method_name = method_name.to_s
22
+ if method_name[-1] == '='
23
+ atr_name = method_name[0..-2]
24
+ have_writer = true
25
+ else
26
+ atr_name = method_name
27
+ have_writer = false
28
+ end
29
+ a = __attribute_filters_virtual[atr_name]
30
+ if a && a != :no_wrap
31
+ if have_writer && method_defined?(atr_name) || !have_writer && method_defined?("#{atr_name}=")
32
+ wrap_virtual_attribute_writer(atr_name)
33
+ end
34
+ end
35
+ method_added_without_afv(method_name)
36
+ end
37
+ alias_method :method_added_without_afv, :method_added
38
+ alias_method :method_added, :method_added_with_afv
39
+
15
40
  private
16
41
 
42
+ def wrap_virtual_attribute_writer(atr_name)
43
+ writer_name = "#{atr_name}=".to_sym
44
+ writer_name_wct = "#{atr_name}_without_ct"
45
+ __attribute_filters_virtual[atr_name] = false
46
+ alias_method(writer_name_wct, writer_name)
47
+ class_eval <<-EVAL
48
+ def #{writer_name}(val)
49
+ attribute_will_change!('#{atr_name}') if val != #{atr_name}
50
+ #{writer_name_wct}(val)
51
+ end
52
+ EVAL
53
+ __attribute_filters_virtual[atr_name] = :tracked
54
+ nil
55
+ end
56
+
17
57
  unless method_defined?(:attr_virtual)
18
58
  # This method creates setter and getter for attributes of the given names
19
59
  # and ensures that changes of their values are tracked.
@@ -22,30 +62,21 @@ module ActiveModel
22
62
  # +attr_virtual+ will overwrite setter. Don't do that.
23
63
  def attr_virtual(*attribute_names)
24
64
  attribute_names.flatten.compact.uniq.each do |atr_name|
25
- writer_name = "#{atr_name}="
26
- atr_name = atr_name.to_sym
27
- attr_reader(atr_name) unless method_defined?(atr_name)
28
- treat_as_virtual(atr_name)
29
- if method_defined?(writer_name)
30
- self.class_eval <<-EVAL
31
- alias_method :#{atr_name}_without_change_tracking=, :#{writer_name}
32
- def #{writer_name}(val)
33
- attribute_will_change!('#{atr_name}') if val != '#{atr_name}'
34
- #{atr_name}_without_change_tracking=(val)
35
- end
36
- EVAL
65
+ atr_name = atr_name.to_s
66
+ writer_name = "#{atr_name}=".to_sym
67
+ if method_defined?(writer_name) && method_defined?(atr_name)
68
+ unless __attribute_filters_virtual.key?(atr_name)
69
+ wrap_virtual_attribute_writer(atr_name)
70
+ end
37
71
  else
38
- self.class_eval <<-EVAL
39
- def #{writer_name}(val)
40
- attribute_will_change!('#{atr_name}') if val != '#{atr_name}'
41
- @#{atr_name} = val
42
- end
43
- EVAL
72
+ __attribute_filters_virtual[atr_name] = :waiting
44
73
  end
45
74
  end
46
75
  nil
47
76
  end # def attr_virtual
77
+ alias_method :has_virtual_attribute, :attr_virtual
48
78
  end # unless method_defined?(:attr_virtual)
79
+
49
80
  end # module ClassMethods
50
81
  end # module AttributeFilters
51
82
  end # module ActiveModel
@@ -454,7 +454,7 @@ module ActiveModel
454
454
  # should be treated as virtual (even not present in the
455
455
  # attributes hash provided by ORM or ActiveModel).
456
456
  #
457
- # @note This method may be used directly if your setter
457
+ # @note This method may be used directly ONLY if your setter
458
458
  # notifies Rails about changes. Otherwise it's recommended
459
459
  # to use the DSL keyword +attr_virtual+.
460
460
  # @param attributes [Array] list of attribute names
@@ -467,7 +467,9 @@ module ActiveModel
467
467
  # @return [AttributeSet] set of attribute names
468
468
  def treat_as_virtual(*args)
469
469
  return __attribute_filters_virtual.deep_dup if args.blank?
470
- __attribute_filters_virtual << args.flatten.compact.map { |atr| atr.to_s }
470
+ args.flatten.compact.each do |atr|
471
+ __attribute_filters_virtual[atr.to_s] = :no_wrap
472
+ end
471
473
  nil
472
474
  end
473
475
  alias_method :attribute_filters_virtual, :treat_as_virtual
@@ -479,20 +481,24 @@ module ActiveModel
479
481
 
480
482
  private
481
483
 
484
+ def __attribute_sets_meta
485
+ @attribute_sets ||= MetaSet.new
486
+ end
487
+
482
488
  def __attribute_filters_semi_real
483
- @__attribute_filters_semi_real ||= ActiveModel::AttributeSet.new
489
+ __attribute_sets_meta[:semi_real] ||= ActiveModel::AttributeSet.new
484
490
  end
485
491
 
486
492
  def __attribute_filters_virtual
487
- @__attribute_filters_virtual ||= ActiveModel::AttributeSet.new
493
+ __attribute_sets_meta[:virtual] ||= ActiveModel::AttributeSet.new
488
494
  end
489
495
 
490
496
  def __attributes_to_sets_map
491
- @__attributes_to_sets_map ||= MetaSet.new(ActiveModel::MetaSet.new.freeze)
497
+ __attribute_sets_meta[:attrs] ||= MetaSet.new(ActiveModel::MetaSet.new.freeze)
492
498
  end
493
499
 
494
500
  def __attribute_sets
495
- @__attribute_sets ||= MetaSet.new(ActiveModel::AttributeSet.new.freeze)
501
+ __attribute_sets_meta[:sets] ||= MetaSet.new(ActiveModel::AttributeSet.new.freeze)
496
502
  end
497
503
 
498
504
  def add_atrs_to_set(set_name, *atrs)
@@ -12,17 +12,8 @@ module ActiveModel
12
12
  # This is a kind of AttributeSet class
13
13
  # but its purpose it so store other information
14
14
  # than attribute names.
15
- class MetaSet < AttributeSet
16
-
17
- # @private
18
- def initialize(*args)
19
- Hash.instance_method(:initialize).bind(self).call(*args)
20
- end
21
-
22
- # @private
23
- def inspect(*args)
24
- Hash.instance_method(:inspect).bind(self).call(*args)
25
- end
15
+ class MetaSet < Hash
16
+ include AttributeFilters::AttributeSetMethods
26
17
 
27
18
  private
28
19
 
@@ -14,11 +14,11 @@ module ActiveModel
14
14
  # @private
15
15
  EMAIL = 'pw@gnu.org'
16
16
  # @private
17
- VERSION = '2.0.0'
17
+ VERSION = '2.0.1'
18
18
  # @private
19
19
  NAME = 'attribute-filters'
20
20
  # @private
21
- SUMMARY = 'Concise way of filtering model attributes in Rails'
21
+ SUMMARY = 'Concise way of filtering and grouping model attributes in Rails'
22
22
  # @private
23
23
  URL = 'https://rubygems.org/gems/attribute-filters/'
24
24
  # @private
@@ -180,13 +180,14 @@ describe ActiveModel::AttributeFilters do
180
180
  before do
181
181
  TestModel.class_eval do
182
182
  include ActiveModel::AttributeFilters::Common
183
- @__attribute_sets = nil
183
+ #@__attribute_sets = nil
184
+ @attribute_sets = nil
184
185
  end
185
186
  @tm = TestModel.new
186
187
  end
187
188
 
188
189
  after do
189
- TestModel.class_eval{@__attribute_sets = nil}
190
+ TestModel.class_eval{@attribute_sets = nil}
190
191
  @tm.attributes_that(:should_be_splitted).should be_empty
191
192
  @tm.attributes_that(:should_be_joined).should be_empty
192
193
  @tm.attributes_that(:should_be_splitted).annotation(:real_name).should == nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attribute-filters
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -36,7 +36,7 @@ cert_chain:
36
36
  YzBobXpodlZadEJ3ZlpyaXRTVmhmQ0xwNXVGd3FDcVkKTkszVElaYVBDaDFT
37
37
  Mi9FUzZ3WE52alErNUVuRUVMOWovcFNFb3A5RFlFQlBhTTJXRFZSNWkwakpU
38
38
  QWFSV3c9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
39
- date: 2012-11-01 00:00:00.000000000 Z
39
+ date: 2012-11-04 00:00:00.000000000 Z
40
40
  dependencies:
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: railties
@@ -320,7 +320,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
320
320
  version: '0'
321
321
  segments:
322
322
  - 0
323
- hash: 3287018804216042017
323
+ hash: -1229665950453395191
324
324
  required_rubygems_version: !ruby/object:Gem::Requirement
325
325
  none: false
326
326
  requirements:
@@ -332,5 +332,5 @@ rubyforge_project: attribute-filters
332
332
  rubygems_version: 1.8.24
333
333
  signing_key:
334
334
  specification_version: 3
335
- summary: Concise way of filtering model attributes in Rails
335
+ summary: Concise way of filtering and grouping model attributes in Rails
336
336
  test_files: []
metadata.gz.sig CHANGED
Binary file