attribute-filters 2.0.0 → 2.0.1

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