attribute-filters 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt CHANGED
@@ -20,10 +20,18 @@ init.rb
20
20
  lib/attribute-filters.rb
21
21
  lib/attribute-filters/active_model_insert.rb
22
22
  lib/attribute-filters/attribute_set.rb
23
+ lib/attribute-filters/attribute_set_annotations.rb
23
24
  lib/attribute-filters/attribute_set_attrquery.rb
24
25
  lib/attribute-filters/attribute_set_enum.rb
25
26
  lib/attribute-filters/attribute_set_query.rb
27
+ lib/attribute-filters/backports.rb
26
28
  lib/attribute-filters/common_filters.rb
29
+ lib/attribute-filters/common_filters/case.rb
30
+ lib/attribute-filters/common_filters/join.rb
31
+ lib/attribute-filters/common_filters/split.rb
32
+ lib/attribute-filters/common_filters/squeeze.rb
33
+ lib/attribute-filters/common_filters/strip.rb
34
+ lib/attribute-filters/dsl_attr_virtual.rb
27
35
  lib/attribute-filters/dsl_filters.rb
28
36
  lib/attribute-filters/dsl_sets.rb
29
37
  lib/attribute-filters/helpers.rb
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Attribute Filters for Rails
2
2
  ===========================
3
3
 
4
- **attribute-filters version `1.3`** (**`Reliable Sausage`**)
4
+ **attribute-filters version `1.4`** (**`Fried Meerkat`**)
5
5
 
6
6
  * https://rubygems.org/gems/attribute-filters
7
7
  * https://github.com/siefca/attribute-filters/tree
@@ -23,12 +23,13 @@ You may want to try it when your Rails application often modifies
23
23
  attribute values that changed recently and uses callbacks to do that.
24
24
 
25
25
  When the number of attributes that are altered in such a way increases,
26
- you can observe the same thing happening with your filtering
26
+ you can observe that the same thing is happening with your filtering
27
27
  methods. That's because each one is tied to some attribute.
28
28
 
29
29
  To refine that process you may write more generic methods
30
30
  for altering attributes. They should be designed to handle
31
31
  common operations and not be tied to certain attributes.
32
+ Attribute Filters helps you do that.
32
33
 
33
34
  Let's see that in action.
34
35
 
@@ -68,6 +69,37 @@ The filtering code is not reusable since it operates on specific attributes.
68
69
 
69
70
  ### After ###
70
71
 
72
+ ```ruby
73
+ class User < ActiveRecord::Base
74
+ include ActiveModel::AttributeFilters::Common
75
+
76
+ strip_attributes :username, :email, :real_name
77
+ downcase_attributes :username, :email
78
+ titleize_attribute :real_name
79
+
80
+ before_validation :filter_attributes
81
+ end
82
+ ```
83
+
84
+ or if you want to have more control:
85
+
86
+ ```ruby
87
+ class User < ActiveRecord::Base
88
+ include ActiveModel::AttributeFilters::Common::Strip
89
+ include ActiveModel::AttributeFilters::Common::Downcase
90
+ include ActiveModel::AttributeFilters::Common::Titleize
91
+
92
+ attributes_that should_be_stripped: [ :username, :email, :real_name ]
93
+ attributes_that should_be_downcased: [ :username, :email ]
94
+ attributes_that should_be_titleized: [ :real_name ]
95
+
96
+ before_validation :strip_attributes
97
+ before_validation :downcase_attributes
98
+ before_validation :titleize_attributes
99
+ end
100
+ ```
101
+
102
+ or if you would like to create filtering methods on your own:
71
103
 
72
104
  ```ruby
73
105
  class User < ActiveRecord::Base
@@ -97,26 +129,18 @@ class User < ActiveRecord::Base
97
129
  end
98
130
  ```
99
131
 
100
- or even shorter:
101
-
102
- ```ruby
103
- class User < ActiveRecord::Base
104
- include ActiveModel::AttributeFilters::Common::Strip
105
- include ActiveModel::AttributeFilters::Common::Downcase
106
- include ActiveModel::AttributeFilters::Common::Titleize
107
-
108
- attributes_that should_be_stripped: [ :username, :email, :real_name ]
109
- attributes_that should_be_downcased: [ :username, :email ]
110
- attributes_that should_be_titleized: [ :real_name ]
132
+ Attributes that should be altered may be simply added
133
+ to the attribute sets that you define and then filtered
134
+ with generic methods that operate on that sets. You can share
135
+ these methods across all of your models by putting them into your own
136
+ handy module that will be included in models that needs it.
111
137
 
112
- before_validation :strip_attributes
113
- before_validation :downcase_attributes
114
- before_validation :titleize_attributes
115
- end
116
- ```
138
+ Alternatively (as presented at the beginning) you can use predefined filtering methods from `ActiveModel::AttributeFilters::Common` module and its submodules. They contain filters
139
+ for changing the case, stripping, squishng, squeezing, joining, splitting
140
+ [and more](http://rubydoc.info/gems/attribute-filters/file/docs/USAGE.md#Predefined_filters).
117
141
 
118
- If you would rather like to group filters by attribute names then
119
- the alternative syntax may be helpful:
142
+ If you would rather like to group filters by attribute names while
143
+ registering them then the alternative syntax may be helpful:
120
144
 
121
145
  ```ruby
122
146
  class User < ActiveRecord::Base
@@ -126,20 +150,10 @@ class User < ActiveRecord::Base
126
150
  end
127
151
  ```
128
152
 
129
- Attributes that should be altered may be simply added
130
- to the attribute sets that you define and then filtered
131
- with generic methods. You can use these methods in all
132
- your models if you wish.
133
-
134
- The last action can be performed by putting the filtering methods into
135
- some base class that all your models inherit form or (better) into your own
136
- handy module that is included in all your models. Alternatively you can
137
- use predefined filters from `ActiveModel::AttributeFilters::Common` module.
138
-
139
153
  Usage and more examples
140
154
  -----------------------
141
155
 
142
- You can use it to filter attributes (as presented above) but you can also
156
+ You can use Attribute Filters to filter attributes (as presented above) but you can also
143
157
  use it to express some logic
144
158
  [on your own](http://rubydoc.info/gems/attribute-filters/file/docs/USAGE.md#Custom_applications).
145
159
 
@@ -2,16 +2,16 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "attribute-filters"
5
- s.version = "1.3.2.20120805175249"
5
+ s.version = "1.4.0.20120824020319"
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-08-05"
10
+ s.date = "2012-08-24"
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"]
14
- s.files = [".rspec", ".yardopts", "ChangeLog", "Gemfile", "Gemfile.lock", "LGPL-LICENSE", "Manifest.txt", "README.md", "Rakefile", "attribute-filters.gemspec", "docs/COPYING", "docs/HISTORY", "docs/LEGAL", "docs/LGPL-LICENSE", "docs/TODO", "docs/USAGE.md", "docs/rdoc.css", "docs/yard-tpl/default/fulldoc/html/css/common.css", "init.rb", "lib/attribute-filters.rb", "lib/attribute-filters/active_model_insert.rb", "lib/attribute-filters/attribute_set.rb", "lib/attribute-filters/attribute_set_attrquery.rb", "lib/attribute-filters/attribute_set_enum.rb", "lib/attribute-filters/attribute_set_query.rb", "lib/attribute-filters/common_filters.rb", "lib/attribute-filters/dsl_filters.rb", "lib/attribute-filters/dsl_sets.rb", "lib/attribute-filters/helpers.rb", "lib/attribute-filters/railtie.rb", "lib/attribute-filters/version.rb", "spec/attribute-filters_spec.rb", "spec/spec_helper.rb", ".gemtest"]
14
+ s.files = [".rspec", ".yardopts", "ChangeLog", "Gemfile", "Gemfile.lock", "LGPL-LICENSE", "Manifest.txt", "README.md", "Rakefile", "attribute-filters.gemspec", "docs/COPYING", "docs/HISTORY", "docs/LEGAL", "docs/LGPL-LICENSE", "docs/TODO", "docs/USAGE.md", "docs/rdoc.css", "docs/yard-tpl/default/fulldoc/html/css/common.css", "init.rb", "lib/attribute-filters.rb", "lib/attribute-filters/active_model_insert.rb", "lib/attribute-filters/attribute_set.rb", "lib/attribute-filters/attribute_set_annotations.rb", "lib/attribute-filters/attribute_set_attrquery.rb", "lib/attribute-filters/attribute_set_enum.rb", "lib/attribute-filters/attribute_set_query.rb", "lib/attribute-filters/backports.rb", "lib/attribute-filters/common_filters.rb", "lib/attribute-filters/common_filters/case.rb", "lib/attribute-filters/common_filters/join.rb", "lib/attribute-filters/common_filters/split.rb", "lib/attribute-filters/common_filters/squeeze.rb", "lib/attribute-filters/common_filters/strip.rb", "lib/attribute-filters/dsl_attr_virtual.rb", "lib/attribute-filters/dsl_filters.rb", "lib/attribute-filters/dsl_sets.rb", "lib/attribute-filters/helpers.rb", "lib/attribute-filters/railtie.rb", "lib/attribute-filters/version.rb", "spec/attribute-filters_spec.rb", "spec/spec_helper.rb", ".gemtest"]
15
15
  s.homepage = "https://rubygems.org/gems/attribute-filters/"
16
16
  s.rdoc_options = ["--title", "Attribute::Filters Documentation", "--quiet"]
17
17
  s.require_paths = ["lib"]
data/docs/HISTORY CHANGED
@@ -1,3 +1,44 @@
1
+ === 1.4.0 / 2012-08-24
2
+
3
+ * major enhancements
4
+
5
+ * Added annotations support
6
+ * Added Split and Join common filters
7
+ * Improved virtual attributes handling (added attr_virtual keyword)
8
+ * All common filters can now handle arrays and hashes
9
+ * API change: additional yeld params changed for DSL instance methods:
10
+ * `for_each_attr_from_set`
11
+ * `filter_attrs_from_set`
12
+ * `operate_on_attrs_from_set`
13
+ * API change: class methods return copies of sets, not originals
14
+
15
+ * minor enhancements
16
+
17
+ * Added `values` and `values_hash` methods to `AttributeSet::Query` for reading values
18
+ * Added `value` method to `AttributeSet::AttrQuery` for reading value
19
+ * Added `each` iterator to custom enumerators
20
+ * Added `attribute_set_simple` instance DSL method for bypassing proxies
21
+ * Added `filter_attributes` instance DSL method for running all predefined filters
22
+ * Added calls forwarding in proxy classes for `instance_eval` and `instance_exec` methods
23
+ * `AttributeSetEnumerable` is now `AttributeSet::Enumerable`
24
+ * `AttirbuteSetEnumerator` is now `AttributeSet::Enumerator`
25
+ * Modified filtering methods so they return specialized enumerator when called whithout a block
26
+ * `for_each_attr_from_set`
27
+ * `filter_attrs_from_set`
28
+
29
+ * major bugfixes
30
+
31
+ * In `AttributeSet::Query`: separated instance level data from class level by adding `dup` where needed
32
+ * In `AttributeSet`: separated instance level data from class level by adding `dup` where needed
33
+
34
+ * minor bugfixes
35
+
36
+ * In `AttributeFilters::Common`: `Squish` submodule included in `Common` module
37
+ * In `AttributeSet::Query`: replaced calls to `send` by calls to `public_send`
38
+ * In `AttributeSet::Query`: replaced calls to `method` by calls to `public_method`
39
+ * In `AttributeSet::AttrQuery`: replaced call to `method` by call to `public_method`
40
+ * In `AttributeFilters::operate_on_attrs_from_set`: replaced calls to `send` by calls to `public_send`
41
+
1
42
  === 1.3.2 / 2012-08-05
2
43
 
3
44
  * minor bugfixes
@@ -8,8 +49,8 @@
8
49
 
9
50
  * major enhancements
10
51
 
11
- * Added accessible?, inaccessible?, protected? attribute checks
12
- * Added all_accessible_attributes, all_protected_attributes, all_inaccessible_attributes
52
+ * Added `accessible?`, `inaccessible?`, `protected?` attribute checks
53
+ * Added `all_accessible_attributes`, `all_protected_attributes`, `all_inaccessible_attributes`
13
54
 
14
55
  * minor enhancements
15
56
 
@@ -19,17 +60,12 @@
19
60
 
20
61
  * major bugfixes
21
62
 
22
- * In AttributeSet::Query proxy class: proxy now uses send() to get attribute values
63
+ * In `AttributeSet::Query`: proxy now uses `send` to get attribute values
23
64
 
24
65
  * major enhancements
25
66
 
26
- * Added valid? and invalid? DSL instance methods
27
- * Added all_attributes instance method
28
-
29
- * minor enhancements
30
-
31
- * Added is_a?() overrides in proxy classes
32
- * Method attributes_to_filter now also takes attribute set as an argument
67
+ * Added `valid?` and `invalid?` DSL instance methods
68
+ * Added `all_attributes` instance method
33
69
 
34
70
  === 1.2.2 / 2012-08-03
35
71
 
@@ -39,8 +75,8 @@
39
75
 
40
76
  * minor enhancements
41
77
 
42
- * Added is_a?() overrides in proxy classes
43
- * Method attributes_to_filter now also takes attribute set as an argument
78
+ * Added `is_a?` overrides to proxy classes
79
+ * Method `attributes_to_filter` can now take attribute set as an argument
44
80
 
45
81
  === 1.2.1 / 2012-07-09
46
82
 
@@ -52,43 +88,43 @@
52
88
 
53
89
  * major bugfixes
54
90
 
55
- * In AttributeSet::Query proxy class: removed typo that caused incorrect processing
91
+ * In `AttributeSet::Query`: removed typo that caused incorrect processing
56
92
 
57
93
  * minor bugfixes
58
94
 
59
- * Added missing respond_to? definitions to proxy classes
95
+ * Added missing `respond_to?` definitions to proxy classes
60
96
 
61
97
  * major enhancements
62
98
 
63
- * Added enumerators (AttributeSetEnumerable and AttributeSetEnumerator)
99
+ * Added enumerators (`AttributeSetEnumerable` and `AttributeSetEnumerator`)
64
100
  * Added predefined filtering methods
65
101
  * Added virtual attributes support in filters
66
102
  * Written the usage instructions
67
103
 
68
104
  * minor enhancements
69
105
 
70
- * Removed the show DSL method from AttrQuery proxy class
106
+ * Removed the show DSL method from `AttrQuery` proxy class
71
107
  * Added checking of model methods that are needed for a proper operation
72
- * Proxy class Query is now viral and tries to wrap the results in its own instances
73
- * Added 'none' and 'one' presence selectors to AttributeSet::Query class
74
- * Added from_attributes_that as the alias for attribute_set instance method
108
+ * Proxy class `AttributeSet::Query` is now viral and tries to wrap the results in its own instances
109
+ * Added `none` and `one` presence selectors to `AttributeSet::Query` class
110
+ * Added `from_attributes_that` as an alias for the `attribute_set` instance method
75
111
 
76
112
  === 1.1.2 / 2012-06-30
77
113
 
78
114
  * major bugfixes
79
115
 
80
- * In operate_on_attrs_from_set: replaced self[attr] and method(attr) calls with send (to be ORM agnostic)
81
- * In attributes_to_filter: inefficient respond_to? calls replaced by the attributes method call
116
+ * In `operate_on_attrs_from_set`: replaced `self[attr]` and `method(attr)` calls with `send` (to be ORM agnostic)
117
+ * In `attributes_to_filter`: inefficient `respond_to?` calls replaced by the attributes method call
82
118
 
83
119
  * major enhancements
84
120
 
85
- * AttributeFilters module can be now used without full Rails stack, just with Active Model loaded
121
+ * `AttributeFilters` module can be now used without full Rails stack, just with Active Model loaded
86
122
 
87
123
  * minor enhancements
88
124
 
89
125
  * Documentation updated
90
- * Added attribute-filters/helpers.rb containing AttributeFiltersHelpers module
91
- * Flags parsing method attr_filter_process_flags moved to AttributeFiltersHelpers as process_flags
126
+ * Added `attribute-filters/helpers.rb` containing `AttributeFiltersHelpers` module
127
+ * Flags parsing method `attr_filter_process_flags` moved to `AttributeFiltersHelpers` as `process_flags`
92
128
  * Added SuperModel and ActiveRecord dependencies for testing purposes
93
129
  * Added first RSpec example
94
130
 
@@ -96,12 +132,12 @@
96
132
 
97
133
  * major enhancement
98
134
 
99
- * API changed; call_attrs_from_set name changed to for_each_attr_from_set, caching removed
135
+ * API changed; `call_attrs_from_set` name changed to `for_each_attr_from_set`, caching removed
100
136
 
101
137
  * minor enhancements
102
138
 
103
139
  * Optimized code for attributes filtering
104
- * Namespace organized (AttributeFilters completely moved under ActiveModel)
140
+ * Namespace organized (`AttributeFilters` moved completely under `ActiveModel` namespace)
105
141
  * Prepared for testing with RSpec
106
142
  * Added custom CSS file for YARD formatter
107
143
 
@@ -110,7 +146,7 @@
110
146
  * minor enhancements
111
147
 
112
148
  * Documentation updated
113
- * Extended arguments parsing for DSL class method attribute_set
149
+ * Extended arguments parsing for DSL class method `attribute_set`
114
150
 
115
151
  === 1.0.1 / 2012-06-28
116
152
 
data/docs/TODO CHANGED
@@ -1,5 +1,20 @@
1
- * add more rspec examples
1
+ * add more rspec examples (+ nil and boolean attributes)
2
2
 
3
- * document changed behaviour of attribute_set instance method
3
+ * add parameter support to squeeze common filter
4
4
 
5
+ * add: changed? all.changed? any.changed? one.changed? none.changed? to attrquery and query proxies
6
+ - they can use methods that internally are checking it
5
7
 
8
+ * add stringify to common filters
9
+ * add numerize to common filters
10
+ * add booleanize to common filters
11
+ * add nullify to common filters
12
+ * add clear to common filters
13
+ * add reverse to common filters
14
+ * add sanitize to common filters (that calls needed)
15
+ -- sanitize_email
16
+ -- sanitize_url
17
+ -- sanitize_string
18
+ -- sanitize_name
19
+ -- sanitize_country
20
+ -- sanitize_iban (future, in a gem attribute-filters-common-iban)
data/docs/USAGE.md CHANGED
@@ -16,10 +16,14 @@ on all attributes that are listed in a set.
16
16
 
17
17
  Attribute sets are instances of
18
18
  [`ActiveModel::AttributeSet`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeSet)
19
- class. They are stored within your model as [class instance variables](http://blog.codegram.com/2011/4/understanding-class-instance-variables-in-ruby).
20
- You cannot and should not interact with them directly
19
+ class. You can create and update sets freely and store them wherever you want,
20
+ but when it comes to models then (at the class-level) you can (and you should) rely
21
+ on a storage that is already prepared to handle sets.
22
+
23
+ The attribute sets assigned to models are stored as [class instance variable](http://blog.codegram.com/2011/4/understanding-class-instance-variables-in-ruby).
24
+ **You cannot and should not interact with that storage directly**
21
25
  but by using dedicated class methods that are available in your models.
22
- These methods will allow you to read or write some data from/to attribute sets.
26
+ These methods will allow you to read or write some data from/to global attribute sets.
23
27
 
24
28
  Attribute Filters are using `AttributeSet` instances
25
29
  not just to store internal data but also to interact
@@ -33,15 +37,26 @@ Note that when sets are returned the convention is that:
33
37
  * **attribute names are strings**
34
38
  * **set names are symbols**
35
39
 
40
+ Also note that once you create a set that is bound to your model you cannot
41
+ remove elements from it and any query returning its contents will give you
42
+ a copy. That's because **model-bound attribute sets should be considered
43
+ a part of the interface**.
44
+
36
45
  ### Defining the attribute sets ###
37
46
 
38
47
  First thing that should be done when using the Attribute Filters
39
48
  is defining the sets of attributes in models.
40
49
 
50
+ You can also create local sets (using `ActiveModel::AttributeSet.new`)
51
+ or a local sets with extra syntactic sugar (using `ActiveModel::AttributeSet.new`)
52
+ but in real-life scenarios you should first create some model-bound sets that
53
+ can be later used by filters and by your own methods.
54
+
41
55
  #### `attribute_set(set_name => attr_names)` ####
42
56
 
43
57
  The [`attribute_set`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/ClassMethods:attribute_set)
44
- (a.k.a `attributes_that`) class method allows to **create or update a set**.
58
+ (a.k.a `attributes_that`) class method allows to **create or update a set** that will be
59
+ tied to a model.
45
60
 
46
61
  Example:
47
62
 
@@ -106,15 +121,15 @@ end
106
121
 
107
122
  ### Querying sets in models ###
108
123
 
109
- When your attribute sets are defined then you can use couple
110
- of class methods to query them, e.g. to check their contents.
124
+ When your "global" attribute sets are defined, you can use couple
125
+ of class methods to query them.
111
126
 
112
127
  #### `attribute_set(set_name)` ####
113
128
 
114
129
  The [`attribute_set`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/ClassMethods:attribute_set)
115
130
  (a.k.a `attributes_that`) class method called with a single argument **returns the attribute set
116
- of the given name**. It will always return an `AttributeSet` instance, even if there is not set of the given name
117
- (in that case the resulting set will be empty).
131
+ of the given name**. It will always return the instance of `AttributeSet` class, even if there
132
+ is no set registered under the given name (in that case the resulting set will be empty).
118
133
 
119
134
  Example:
120
135
 
@@ -123,6 +138,8 @@ Example:
123
138
  # => #<ActiveModel::AttributeSet: {"real_name"}>
124
139
  ```
125
140
 
141
+ Note that the returned set will be a copy of the original set stored within your model.
142
+
126
143
  Instead of `attribute_set` you may also use one of the aliases:
127
144
 
128
145
  * `attributes_that`, `attributes_that_are`, `are_attributes_that_are`, `from_attributes_that_are`,
@@ -152,6 +169,8 @@ Instead of `attribute_sets` you may also use one of the aliases:
152
169
 
153
170
  * `attributes_sets`, `properties_sets`
154
171
 
172
+ Note that the returned sets will be copies of the original sets stored within your model.
173
+
155
174
  #### `attribute_set` ####
156
175
 
157
176
  The [`attribute_set`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/ClassMethods:attribute_set)
@@ -207,7 +226,7 @@ is a wrapper that calls `attributes_to_sets` which returns
207
226
  a hash containing **all filtered attributes and arrays of sets**
208
227
  that the attributes belong to.
209
228
 
210
- ### Querying sets in objects ###
229
+ ### Querying sets in model objects ###
211
230
 
212
231
  It is possible to access attribute sets from within the ActiveModel (or ORM, like ActiveRecord) objects.
213
232
  To do that you may use instance methods that are designed for that purpose.
@@ -215,7 +234,7 @@ To do that you may use instance methods that are designed for that purpose.
215
234
  #### `attribute_set` ####
216
235
 
217
236
  The [`attribute_set`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters:attribute_set)
218
- method called withount an argument **returns the attribute set containing all attributes for a current object**.
237
+ instance method called withount an argument **returns the attribute set object containing all attributes known in a current object**.
219
238
 
220
239
  Example:
221
240
 
@@ -229,9 +248,9 @@ It works the same as the `all_attributes` method.
229
248
  #### `attribute_set(set_name)` ####
230
249
 
231
250
  The [`attribute_set`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters:attribute_set)
232
- (a.k.a `attributes_that`) method called with a single argument **returns the attribute set
251
+ (a.k.a `attributes_that`) instance method called with a single argument **returns a copy of the attribute set
233
252
  of the given name**. It won't return the exact set object but a duplicate.
234
- It will always return an `AttributeSet` instance, even if there is not set of the given name
253
+ It will always return an `AttributeSet` instance, even if there is no set of the given name defined for a model
235
254
  (in that case the resulting set will be empty).
236
255
 
237
256
  Example:
@@ -241,6 +260,24 @@ Example:
241
260
  # => #<ActiveModel::AttributeSet: {"real_name", "username", "email"}>
242
261
  ```
243
262
 
263
+ The returned object in transparently wrapped in a proxy class instance that allows you
264
+ to apply additional methods and keywords to it. See the section
265
+ ['Syntactic sugar for queries'](http://rubydoc.info/gems/attribute-filters/file/docs/USAGE.md#Syntactic_sugar_for_queries)
266
+ for more info.
267
+
268
+ There are also variants of this method that differ in a kind of taken argument:
269
+
270
+ * `attribute_set(set_object)`
271
+
272
+ > Allows to wrap an existing attribute set instance (e.g. created locally) with transparent proxy instance.
273
+ > The resulting object will look like the same set but will be decorated with additional syntactic sugar.
274
+
275
+ * `attribute_set(any_object)`
276
+
277
+ > Allows to create local set that will be initialized with the given object (usually an array) that may not
278
+ > be a `String`, a `Symbol` or an `AttributeSet` (these are reserved for the variants above). The resulting
279
+ > object (a new `AttributeSet` instance) is also wrapped in a proxy.
280
+
244
281
  Instead of `attribute_set` you may also use one of the aliases:
245
282
 
246
283
  * `attributes_that_are`, `from_attributes_that`, `are_attributes_that_are`, `from_attributes_that_are`,
@@ -252,7 +289,8 @@ Instead of `attribute_set` you may also use one of the aliases:
252
289
 
253
290
  The [`attribute_sets`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters:attribute_sets)
254
291
  method returns a hash containing **all the defined attribute sets** indexed by their names.
255
- It won't return the exact internal hash but a duplicate.
292
+ It won't return the exact internal hash but a duplicate and every set within it will also be a duplicate
293
+ of the original one.
256
294
 
257
295
  Example:
258
296
 
@@ -382,6 +420,7 @@ Example:
382
420
 
383
421
  Note that the returned hash will have a default value set to instance of the `AttributeSet`.
384
422
  It won't return the exact internal hash but a duplicate.
423
+
385
424
  If you'll try to get the value of an element that doesn't exist **the empty set will be returned**.
386
425
 
387
426
  Instead of `attributes_to_sets` you may also use one of the aliases:
@@ -423,8 +462,9 @@ practice there are two groups of methods with two sets of DSL features.
423
462
 
424
463
  First group (querying attribute sets):
425
464
 
426
- * [`attribute_set`](#attribute_set_set_name_0) and aliases
427
- * [`attributes_to_filter`](#attributes_to_filter_set_name_____)
465
+ * [`attribute_set`](#attribute_set0) and aliases
466
+ * [`attribute_set(set_name)`](#attribute_set_set_name_1) and aliases
467
+ * [`attributes_to_filter`](#attributes_to_filter_set_____)
428
468
 
429
469
  Second group (querying attributes for sets they belong to):
430
470
 
@@ -435,8 +475,9 @@ Second group (querying attributes for sets they belong to):
435
475
  Querying attribute sets uses two instance methods available
436
476
  in your models:
437
477
 
438
- * [`attribute_set`](#attribute_set_set_name_0) and aliases
439
- * [`attributes_to_filter`](#attributes_to_filter_set_name_____)
478
+ * [`attribute_set`](#attribute_set0) and aliases
479
+ * [`attribute_set(set_name)`](#attribute_set_set_name_1) and aliases
480
+ * [`attributes_to_filter`](#attributes_to_filter_set_____)
440
481
 
441
482
  The output of calling these methods is an `AttributeSet` instance
442
483
  wrapped within a transparent proxy class instance
@@ -489,7 +530,7 @@ Let's do it without any fancy DSL methods:
489
530
  ```ruby
490
531
  u = User.first
491
532
  u.attributes_that(:should_be_stripped).all? do |attribute_name|
492
- u.send(attribute_name).present?
533
+ u.public_send(attribute_name).present?
493
534
  end
494
535
  # => false
495
536
  ```
@@ -745,9 +786,9 @@ for altering attribute values: `for_attributes_that` and
745
786
 
746
787
  Filtering attributes basically means calling a proper
747
788
  method that will collect the attributes (matching certain
748
- criteria and belonging to the given set) and call some code block
749
- used that will either produce their new values or just call
750
- some method on each of them.
789
+ criteria and belonging to the given set) and calling some code block
790
+ that will either produce new values or just call some method on each
791
+ of current attribute value and name.
751
792
 
752
793
  #### `filter_attrs_from_set(set_name,...)` ####
753
794
 
@@ -756,6 +797,14 @@ The [`filter_attrs_from_set`](http://rubydoc.info/gems/attribute-filters/ActiveM
756
797
  It takes optional arguments and a block. The result of evaluating a block will become a new value for a processed
757
798
  attribute. Optional arguments will be passed as the last arguments of a block.
758
799
 
800
+ The evaluated block can make use of the following arguments that are passed to it:
801
+
802
+ * `attribute_value` [Object] - current attribute value that should be altered
803
+ * `attribute_name` [String] - a name of currently processed attribute
804
+ * `set_object` [Object] - currently processed set that attribute belongs to
805
+ * `set_name` [Symbol] - a name of the processed attribute set
806
+ * `args` [Array] - an optional arguments passed to the method
807
+
759
808
  By default only existing, changed and non-blank attributes are processed.
760
809
  You can change that behavior by adding a flags as the first arguments:
761
810
 
@@ -772,7 +821,7 @@ Example:
772
821
  before_validation :lolize
773
822
 
774
823
  def lolize
775
- filter_attributes_that(:should_be_lolized, "lol") do |attribute_value, set_name, attribute_name, *args|
824
+ filter_attributes_that(:should_be_lolized, "lol") do |attribute_value, attribute_name, set_object, set_name, *args|
776
825
  [attribute_value, set_name.to_s, attribute_name, *args.flatten].join('-')
777
826
  end
778
827
  end
@@ -785,6 +834,12 @@ Example:
785
834
  # => "john-should_be_lolized-username-lol"
786
835
  ```
787
836
 
837
+ You can pass a set object (`AttributeSet` instance) instead of set name as an argument. The method
838
+ will then work on that local data with one difference: the `set_name` passed to a block will be set to `nil`.
839
+
840
+ If the block is not given then the method returns an enumerator which can be used to query method later
841
+ (when the block is present).
842
+
788
843
  Instead of `filter_attrs_from_set` you may also use one of the aliases:
789
844
 
790
845
  * `attribute_filter_for_set`, `filter_attributes_which`,
@@ -799,6 +854,14 @@ It takes optional arguments and a block. The result of evaluating the given bloc
799
854
  but you can interact with the attribute object from within a block.
800
855
  Optional arguments will be passed as the last arguments of a block.
801
856
 
857
+ The evaluated block can make use of the following arguments that are passed to it:
858
+
859
+ * `attribute_value` [Object] - current attribute value that should be altered
860
+ * `attribute_name` [String] - a name of currently processed attribute
861
+ * `set_object` [Object] - currently processed set that attribute belongs to
862
+ * `set_name` [Symbol] - a name of the processed attribute set
863
+ * `args` [Array] - an optional arguments passed to the method
864
+
802
865
  By default only existing, changed and non-blank attributes are processed.
803
866
  You can change that behavior by adding a flags as the first arguments:
804
867
 
@@ -815,7 +878,7 @@ Example:
815
878
  before_validation :lolize
816
879
 
817
880
  def lolize
818
- for_attributes_that(:should_be_lolized, :process_blank, "lol") do |attribute_object, set_name, attribute_name, *args|
881
+ for_attributes_that(:should_be_lolized, :process_blank, "lol") do |attribute_value, attribute_name, set_object, set_name, *args|
819
882
  attribute_object << '-' << [set_name.to_s, attribute_name, *args.flatten].join('-')
820
883
  end
821
884
  end
@@ -828,6 +891,12 @@ Example:
828
891
  # => "john-should_be_lolized-username-lol"
829
892
  ```
830
893
 
894
+ You can pass a set object (`AttributeSet` instance) instead of set name as an argument. The method
895
+ will then work on that local data with one difference: the `set_name` passed to a block will be set to `nil`.
896
+
897
+ If the block is not given then the method returns an enumerator which can be used to query method later
898
+ (when the block is present).
899
+
831
900
  Instead of `filter_attrs_from_set` you may also use one of the aliases:
832
901
 
833
902
  * `attribute_call_for_set`, `call_attrs_from_set`,
@@ -867,107 +936,1121 @@ if the filtering operation occured.
867
936
 
868
937
  There are cases that virtual attributes (the ones that does not really exist in a database)
869
938
  are to be filtered. By default it won't happen since all the filtering methods assume
870
- the presence of any processed attribute as "the real" attribute. There are three ways
939
+ the presence of any processed attribute as 'a real' attribute. There are three ways
871
940
  to overcome that problem.
872
941
 
873
- First (the messy one) is to add/update a virtual attribute in `attributes` hash each time
874
- a setter for your attribute is called. It interacts with the Rails internals so
875
- use it at your own risk. You should also register attribute as changed if any changes
876
- are made on it. To do that look at your ORM's documentation and see
877
- [`ActiveModel::Dirty`](http://api.rubyonrails.org/classes/ActiveModel/Dirty.html).
942
+ #### Unconditional filtering ####
943
+
944
+ The first way is to pass the `:no_presence_check` and `:process_all` flags to the filtering method.
945
+ That causes filter to be executed for each attribute from a set without any conditions (no checking
946
+ for presence of setter/getter method and no checking for changes).
947
+
948
+ Be aware that by doing that you take full responsibility for attribute set names added to your set.
949
+ If you put a name of nonexistent attribute then you may get ugly error later.
950
+
951
+ With that approach you can filter virtual attributes that are inaccessible to controllers
952
+ and don't show up when model is queried for known attributes. Using it may also cause some filters
953
+ to be executed more often than needed, since the changes tracking is disabled (`process_all`).
954
+
955
+ Example:
956
+
957
+ ```ruby
958
+ class User
959
+
960
+ # declare a virtual attribute
961
+ attr_accessor :real_name
962
+
963
+ # define a set
964
+ attributes_that :should_be_splitted => [ :real_name ]
965
+
966
+ # register a callback method
967
+ before_validation :split_attributes
968
+
969
+ # create a filtering method
970
+ def split_attributes
971
+ for_attributes_that(:should_be_splitted, :no_presence_check, :process_all) do |value|
972
+ names = value.split(' ')
973
+ self.first_name = names[0] # assuming first_name exists in a database
974
+ self.last_name = names[1] # assuming last_name exists in a database
975
+ end
976
+ end
977
+
978
+ end
979
+ ```
980
+
981
+ #### Marking as semi-real ####
982
+
983
+ The second way is to use `treat_attributes_as_real` (or simply `treat_as_real`) clause in your model
984
+ (available from version 1.2.0 of Attribute Filters). That approach may be applied to attributes
985
+ that aren't (may not or should not be) tracked for changes and aren't (may not or should not be) accessible
986
+ (to a controller) nor marked as protected.
987
+
988
+ There are two main differences from the unconditional filtering. First is that marking attribute
989
+ as real causes it to be added to the list of all known attributes that is returned by `all_attributes_set`
990
+ (a.k.a `all_attributes`). The second is that nothing ugly will happen if there will be non-existent
991
+ attributes in a set when filtering method kicks in. That's because the filtering method doesn't require
992
+ `:no_presence_check` flag to pick up such attributes. Attributes that could not be handled are simply ignored.
993
+
994
+ The `:process_all` flag is also not needed since all virtual attributes marked as real are by default
995
+ not checked for changes.
996
+
997
+ Example:
998
+
999
+ ```ruby
1000
+ class User
1001
+
1002
+ # declare a virtual attribute
1003
+ attr_accessor :real_name
1004
+
1005
+ # mark the attribute as real
1006
+ treat_as_real :real_name
1007
+
1008
+ # define a set
1009
+ attributes_that :should_be_splitted => [ :real_name ]
1010
+
1011
+ # register a callback method
1012
+ before_validation :split_attributes
1013
+
1014
+ # create a filtering method
1015
+ def split_attributes
1016
+ for_attributes_that(:should_be_splitted) do |value|
1017
+ names = value.split(' ')
1018
+ self.first_name = names[0] # assuming first_name exists in a database
1019
+ self.last_name = names[1] # assuming last_name exists in a database
1020
+ end
1021
+ end
1022
+
1023
+ end
1024
+ ```
1025
+
1026
+ Be aware that the virtual attributes declared that way will always be filtered
1027
+ since there is no way to know whether they have changed or not.
1028
+ That may lead to strange results under some circumstances, e.g. in case of
1029
+ cutting some part of a string stored in a virtual attribute by using a filter
1030
+ registered with `before_save`; if such operation will be performed
1031
+ more than once then the filtering will be performed more than once too.
1032
+
1033
+ The presence of virtual attributes is tested by checking if both, a setter and a getter,
1034
+ methods exist, unless the `no_presence_check` flag is passed to a filtering method.
1035
+ If both accessors don't exist then the attribute is not processed.
1036
+
1037
+ This approach can be easily used with predefined filters.
1038
+
1039
+ #### Marking as trackable ####
1040
+
1041
+ Since version 1.4.0 of Attribute Filters the **recommended way of dealing with
1042
+ virtual attributes** is to make use of changes tracking available in Active Model.
1043
+ To do that look at your ORM's documentation and see
1044
+ [`ActiveModel::Dirty`](http://api.rubyonrails.org/classes/ActiveModel/Dirty.html)
1045
+ and a method `attribute_will_change` that it contains. Just call that method from within
1046
+ your setter and you're done.
1047
+
1048
+ This approach can be easily used with predefined filters.
1049
+
1050
+ Conditions:
1051
+
1052
+ * Use `attr_accessible` or `attr_protected` to mark the attribute as known
1053
+ * Use your own setter for notifying that attribute value has changed or `attr_virtual`
1054
+
1055
+ The benefit of that approach is that a filter will never be called redundantly contrary
1056
+ to previous methods.
1057
+
1058
+ Example:
1059
+
1060
+ ```ruby
1061
+ class User < ActiveRecord::Base
1062
+
1063
+ # declare a virtual attribute
1064
+ attr_reader :real_name
1065
+ attr_accessible :real_name
1066
+
1067
+ # define a set
1068
+ attributes_that :should_be_splitted => [ :real_name ]
1069
+
1070
+ # register a callback method
1071
+ before_validation :split_attributes
1072
+
1073
+ # create writer that notifies Active Model about changes
1074
+ def real_name=(val)
1075
+ attribute_will_change!('real_name') if val != real_name
1076
+ @real_name = val
1077
+ end
1078
+
1079
+ # create a filtering method
1080
+ def split_attributes
1081
+ for_attributes_that(:should_be_splitted) do |value|
1082
+ names = value.split(' ')
1083
+ self.first_name = names[0] # assuming first_name exists in a database
1084
+ self.last_name = names[1] # assuming last_name exists in a database
1085
+ end
1086
+ end
1087
+
1088
+ end
1089
+ ```
1090
+
1091
+ You can also use built-in DSL keyword **`attr_virtual`** that will create setter and getter
1092
+ for you:
1093
+
1094
+ ```ruby
1095
+ class User < ActiveRecord::Base
1096
+
1097
+ # declare a virtual attribute
1098
+ attr_virtual :real_name
1099
+ attr_accessible :real_name
1100
+
1101
+ # define a set
1102
+ attributes_that :should_be_splitted => [ :real_name ]
1103
+
1104
+ # register a callback method
1105
+ before_validation :split_attributes
1106
+
1107
+ # create a filtering method
1108
+ def split_attributes
1109
+ for_attributes_that(:should_be_splitted) do |value|
1110
+ names = value.split(' ')
1111
+ self.first_name = names[0]
1112
+ self.last_name = names[1]
1113
+ end
1114
+ end
1115
+
1116
+ end
1117
+ ```
1118
+
1119
+ #### Marking as trackable and semi-real ####
1120
+
1121
+ That's a variant of the recommended way of dealing with virtual attributes. It may be useful
1122
+ if you don't want to (or cannot) add virtual attributes to access lists using `attr_accessible`
1123
+ or `attr_protected`.
1124
+
1125
+ Example:
1126
+
1127
+ ```ruby
1128
+ class User < ActiveRecord::Base
1129
+
1130
+ # declare a virtual attribute
1131
+ attr_virtual :real_name
1132
+
1133
+ # mark the attribute as real
1134
+ treat_as_real :real_name
1135
+
1136
+ # tell the engine that all virtual attributes
1137
+ # are tracked for changes and it should pick from changed
1138
+ # not from all
1139
+ virtual_attributes_are_tracked
1140
+
1141
+ # define a set
1142
+ attributes_that :should_be_splitted => [ :real_name ]
1143
+
1144
+ # register a callback method
1145
+ before_validation :split_attributes
1146
+
1147
+ # create a filtering method
1148
+ def split_attributes
1149
+ for_attributes_that(:should_be_splitted) do |value|
1150
+ names = value.split(' ')
1151
+ self.first_name = names[0]
1152
+ self.last_name = names[1]
1153
+ end
1154
+ end
1155
+
1156
+ end
1157
+ ```
1158
+
1159
+ Annotations
1160
+ -----------
1161
+
1162
+ Annotations are portions of data that you can bind to attribute names residing within attribute sets.
1163
+ What for? To store something that is related to the specific attribute and that should be memorized within
1164
+ a set and/or its copies (if any). You can annotate each attribute name using key -> value pairs where the key is always a symbol and value is any kind of object you want. Only existing attributes can be annotated and deleting attribute
1165
+ will remove annotations that are assigned to it.
1166
+
1167
+ When you copy a set, create difference or intersection of attribute sets, any existing annotations are also copied.
1168
+ If the operation creates a sum or joins sets then the annotations are mixed too.
1169
+
1170
+ The annotations are used by some of the predefined filters described later to precise operations that have
1171
+ to be taken (e.g. specifying separator string for attribute joining filter and so on).
1172
+
1173
+ ### Creating annotations ###
1174
+
1175
+ You can create annotations during defining a set or you can add them later with `annotate_attribute_set`.
1176
+ In case of local sets you can also use a method called `annotate`.
1177
+
1178
+ #### When defining sets ####
1179
+
1180
+ When using the first method just replace attrbute name with a hash, where attribute name is a key
1181
+ and annotations are another hash containing keys and values.
1182
+
1183
+ Example:
1184
+
1185
+ ```ruby
1186
+ class User
1187
+ # Set name: cool
1188
+ # Attribute name: email
1189
+ # Annotation key: some_key
1190
+ # Annotation value: some value
1191
+
1192
+ attributes_that_are cool: { :email => { :some_key => "some value" } }
1193
+ end
1194
+ ```
1195
+
1196
+ The above will annotate `email` attribute within a set `should_be_something` with `some_key` => "some value" pair.
1197
+ You can mix annotated attributes with unannotated; just put the last ones in front of an array:
878
1198
 
879
- The second way is to pass the `:no_presence_check` and `process_all` flags to the filtering method.
880
- Be aware that by doing that you take full responsibility for attribute set defined in your model.
881
- If you put a name of nonexistent attribute to the set then later you may get ugly error.
1199
+ ```ruby
1200
+ class User
1201
+ attributes_that_are cool: [ :some_unannotated, { :email => { :some_key => "some value" } } ]
1202
+ end
1203
+ ```
1204
+
1205
+ or
1206
+
1207
+ ```ruby
1208
+ class User
1209
+ attributes_that_are :cool => [ :some_unannotated, { :email => { :some_key => "some value" } } ]
1210
+ end
1211
+ ```
1212
+
1213
+ #### After defining sets ####
1214
+
1215
+ To create annotations for class-level sets use the [`annotate_attribute_set`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/ClassMethods.html#annotate_attribute_set-instance_method) method (an its aliases):
1216
+
1217
+ * **`annotate_attributes_that_are`**
1218
+ * **`annotate_attributes_that`**
1219
+ * **`annotate_attributes_are`**
1220
+ * **`annotate_attributes_for`**
1221
+ * **`annotate_attributes_set`**
1222
+ * **`annotate_properties_that`**
1223
+ * **`annotate_attributes`**
1224
+
1225
+ Example:
1226
+
1227
+ ```ruby
1228
+ class User
1229
+ attributes_that_are cool: [ :some_unannotated, :email, :username ]
1230
+ annotate_attributes_that_are cool: [ :email, :some_key, "some value" ]
1231
+ annotate_attributes_that_are :cool => { :username => { :some_key => "some value", :other_k => 'other v' } }
1232
+ end
1233
+ ```
1234
+
1235
+ Caution: Annotating attributes that aren't present in a set
1236
+ with `annotate_attribute_set` will raise an error.
1237
+
1238
+ #### Annotating local sets ####
1239
+
1240
+ * **`annotate`**
1241
+
1242
+ Example:
1243
+
1244
+ ```ruby
1245
+ class User
1246
+ def some_method
1247
+ s = ActiveModel::AttributeSet.new([:first_element, :second_element])
1248
+ s.annotate(:first_element, :key, 'value')
1249
+ end
1250
+ end
1251
+ ```
1252
+
1253
+ ### Removing annotations ###
882
1254
 
883
- The third way is to use `treat_attributes_as_real` (or simply `treat_as_real`) clause in your model
884
- (available from version 1.2.0 of Attribute Filters). That's **the preferred one**.
1255
+ To remove annotations locally (from sets that are not directly bound to models) you can use:
885
1256
 
886
- Just add your virtual attributes to the model like that:
1257
+ * **`delete_annotation(attribute_name, annotation_key)`** - to delete specified annotation key for the given attribute
1258
+ * **`delete_annotations(attribute_name)`** - to delete all annotations for an attribute of the given name
1259
+ * **`remove_annotations()`** - to remove all annotations from a set
1260
+
1261
+ Be aware that using these method to delete annotations from class-level sets won't work.
1262
+ That's because you'll always get a copy when querying these sets. However there are methods that
1263
+ will work in a model:
1264
+
1265
+ * **`delete_annotations_from_set(set_name, attribute, *annotation_keys)`** - to delete annotation keys for the given attribute
1266
+ * **`delete_annotations_from_set(set_name, attribute)`** - to delete all annotation keys for the given attribute
1267
+ * **`delete_annotations_from_set(set_name => *attributes)`** - to delete all annotation keys for the given attribute
1268
+ * **`delete_annotations_from_set(set_name => { attribute => keys})`** - to delete specified annotation keys for the given attributes
1269
+
1270
+ Example:
1271
+
1272
+ ```ruby
1273
+ class User
1274
+ attributes_that_are cool: [ :some_unannotated, :email ]
1275
+
1276
+ # That will work
1277
+ delete_annotations_from_set cool: :email
1278
+ delete_annotations_from_set cool: [ :email, :key_one ]
1279
+ delete_annotation_from_set cool: { :email => [:key_one, :other_key], :name => :some_key }
1280
+
1281
+ # That won't affect the global set called 'cool'
1282
+ # since we have its copy here, not the original.
1283
+ def some_method
1284
+ attributes_that_are(:cool).delete_annotation(:email)
1285
+ end
1286
+
1287
+ # That won't affect the global set called 'cool'
1288
+ # since we have its copy here, not the original.
1289
+ attributes_that_are(:cool).delete_annotation(:email)
1290
+ end
1291
+ ```
1292
+
1293
+ ### Updating annotations ###
1294
+
1295
+ Calling `annotate` method again on a set or redefining set at a class-level allows to add annotations
1296
+ or modify their keys.
1297
+
1298
+ Example:
887
1299
 
888
1300
  ```ruby
889
1301
  class User
890
- treat_as_real :some, :virtual, :attributes
1302
+ attributes_that_are :cool => { :email => { :some_key => "some value" } }
1303
+ attributes_that_are cool: { :email => { :other_key => "other_value" } }
1304
+ attributes_that_are cool: { :email => { :some_key => "another_value" } }
1305
+ annotate_attributes_that_are :cool, :email, :some_key => "x"
1306
+ delete_annotation_from_set :cool => { :email => :other_key }
891
1307
  end
1308
+
1309
+ # In the result there will be only one annotation key left;
1310
+ # :some_key with the value of "x"
892
1311
  ```
893
1312
 
894
- Be aware that the virtual attributes will always be filtered regardless of `process_all` flag,
895
- since there is no way to know whether they have changed or not. If you are somehow updating
896
- `changes` (or `changed_attributes` hash) on your own then you can modify that behavior
897
- for specific model by putting `filter_virtual_attributes_that_have_changed` keyword into it:
1313
+ Caution: Annotating attributes that aren't present in a set
1314
+ with `annotate_attribute_set` or by using `annotate` method will raise an error.
1315
+
1316
+ Be aware that updating annotations directly (using `annotate` method) won't work on sets
1317
+ defined directly in classes describing models. That's because you'll always get
1318
+ a copy when querying these sets.
898
1319
 
899
1320
  ```ruby
900
1321
  class User
901
- filter_virtual_attributes_that_have_changed
902
- treat_as_real :some, :virtual, :attributes
1322
+ attributes_that_are :cool => { :email => { :some_key => "some value" } }
1323
+
1324
+ # Calling `some_method` won't work on 'cool' global set.
1325
+ def some_method
1326
+ attributes_that_are(:cool).delete_annotation(:email, :other_key)
1327
+ end
903
1328
  end
904
1329
  ```
905
1330
 
906
- The presence of virtual attributes is tested by checking
907
- if both, a setter and a getter, methods exist,
908
- unless the `no_presence_check` flag is passed
909
- to a filtering method.
1331
+ ## Querying annotations ###
1332
+
1333
+ To check if a set has any annotations you can use one of the methods:
1334
+
1335
+ * **`has_annotation?`** - checks if a set has any annotations
1336
+ * **`has_annotations?`** - checks if a set has any annotations
1337
+ * **`has_annotation?(attribute_name)`** - checks if a set has any annotations for the given attribute
1338
+ * **`has_annotation?(attribute_name, *annotation_keys)`** - checks if a set has any annotation key for the given attribute
1339
+
1340
+ To read annotations you can use :
1341
+
1342
+ * **`annotation(attribute_name)`** - gets a hash of annotations or returns nil
1343
+ * **`annotation(attribute_name, *keys)`** - gets an array annotation values for the given keys (puts nils if key is missing) or returns nil
1344
+
1345
+ Example:
1346
+
1347
+ ```ruby
1348
+ class User
1349
+ attributes_that_are cool: [ :some_unannotated, :email => { :x => :y } ]
1350
+
1351
+ def q
1352
+ attributes_that_are(:cool).annotation(:email, :x, :z)
1353
+ end
1354
+
1355
+ def qq
1356
+ attributes_that_are(:cool).annotation(:nope, :x, :z)
1357
+ end
1358
+
1359
+ # Calling q will return an array: [:y, nil]
1360
+ # Calling qq will return nil since attribute is not present (or not annotated)
1361
+
1362
+ end
1363
+ ```
910
1364
 
911
1365
  Predefined filters
912
1366
  ------------------
913
1367
 
914
1368
  Predefined filters are ready-to-use methods
915
1369
  for filtering attributes. You just have to call them
916
- or pass their names to callback hooks.
1370
+ or register them as [callbacks](http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html).
917
1371
 
918
- To use predefined filters you have to manually
1372
+ To use all predefined filters you have to manually
919
1373
  include the [`ActiveModel::AttributeFilters::Common`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/Common)
920
- module. If you don't want to include portions of code that you'll never use, you can also include some filters selectively. To do that just include just a submodule containing certain filtering method.
921
-
922
- Here is a list of the predefined filtering methods:
923
-
924
- * `capitalize_attributes` (submodule: `Capitalize`)
925
- * `fully_capitalize_attributes` (submodule: `Capitalize`)
926
- * `titleize_attributes` (submodule: `Titleize`)
927
- * `downcase_attributes` (submodule: `Downcase` or `Case`)
928
- * `upcase_attributes` (submodule: `Upcase` or `Case`)
929
- * `strip_attributes` (submodule: `Strip`)
930
- * `squeeze_attributes` (submodule: `Squeeze`)
931
- * `squish_attributes` (submodule: `Squish`)
1374
+ module. That will include **all available filtering methods** into your model.
932
1375
 
933
1376
  Example:
934
1377
 
935
1378
  ```ruby
936
1379
  class User < ActiveRecord::Base
937
1380
  include ActiveModel::AttributeFilters::Common
938
-
939
- the_attribute user: [:should_be_squished, :should_be_downcased ]
940
- the_attribute email: [:should_be_squished, :should_be_downcased ]
941
- the_attribute name: [:should_be_squished, :should_be_downcased, :should_be_titleized ]
942
-
943
- before_validation :squish_attributes
1381
+
1382
+ the_attribute name: [:should_be_downcased, :should_be_titleized ]
1383
+
944
1384
  before_validation :downcase_attributes
945
1385
  before_validation :titleize_attributes
946
1386
  end
947
1387
  ```
948
1388
 
949
- or (better):
1389
+ If you don't want to include portions of code that you'll never use, you can include some filters selectively. To do that include just a submodule containing certain filtering method:
950
1390
 
951
1391
  ```ruby
952
1392
  class User < ActiveRecord::Base
953
- include ActiveModel::AttributeFilters::Common::Squish
954
1393
  include ActiveModel::AttributeFilters::Common::Downcase
955
1394
  include ActiveModel::AttributeFilters::Common::Titleize
956
-
957
- the_attribute user: [:should_be_squished, :should_be_downcased ]
958
- the_attribute email: [:should_be_squished, :should_be_downcased ]
959
- the_attribute name: [:should_be_squished, :should_be_downcased, :should_be_titleized ]
960
-
961
- before_validation :squished_attributes
1395
+
1396
+ the_attribute name: [:should_be_downcased, :should_be_titleized ]
1397
+
962
1398
  before_validation :downcase_attributes
963
1399
  before_validation :titleize_attributes
964
1400
  end
965
1401
  ```
966
1402
 
1403
+ As you can see, to use any filter you should include a proper submodule, add attribute
1404
+ names to a proper set and register a callback. The name of a set
1405
+ that a filtering method will use is predetermined and follows the
1406
+ convention that it should correspond to the name of a filter. So the
1407
+ set used by squeezing filter will be named `should_be_squeezed`.
1408
+
1409
+ For example, to squeeze attributes `name` and `email` you can write:
1410
+
1411
+ ```ruby
1412
+ class User < ActiveRecord::Base
1413
+ include ActiveModel::AttributeFilters::Common::Squeeze
1414
+ attributes_that should_be_sqeezed: [:email, :name]
1415
+ before_validation :squeeze_attributes
1416
+ end
1417
+ ```
1418
+
1419
+ The filtering methods usually come with class-level DSL methods
1420
+ that are a simple wrappers calling `the_attribute`. So you can
1421
+ also write:
1422
+
1423
+ ```ruby
1424
+ class User < ActiveRecord::Base
1425
+ include ActiveModel::AttributeFilters::Common::Squeeze
1426
+ squeeze_attributes :email, :name
1427
+ before_validation :squeeze_attributes
1428
+ end
1429
+ ```
1430
+
1431
+ ### Calling all at once ###
1432
+
1433
+ There is a special method called
1434
+ [`filter_attributes`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters.html#filter_attributes-instance_method) that can be registered as a callback. It will call all possible (known) filtering methods
1435
+ in a predetermined order.
1436
+
1437
+ Example:
1438
+
1439
+ ```ruby
1440
+ class User < ActiveRecord::Base
1441
+ include ActiveModel::AttributeFilters::Common::Squeeze
1442
+ include ActiveModel::AttributeFilters::Common::Capitalize
1443
+
1444
+ squeeze_attributes :email, :name
1445
+ capitalize_attributes :name
1446
+
1447
+ before_validation :filter_attributes
1448
+ end
1449
+ ```
1450
+
1451
+ Use this method if you're really lazy.
1452
+ You can also create your own method like that and call all needed filters there:
1453
+
1454
+ ```ruby
1455
+ class User < ActiveRecord::Base
1456
+ include ActiveModel::AttributeFilters::Common::Squeeze
1457
+ include ActiveModel::AttributeFilters::Common::Capitalize
1458
+
1459
+ squeeze_attributes :email, :name
1460
+ capitalize_attributes :name
1461
+
1462
+ before_validation :my_total_filtering_method
1463
+
1464
+ def my_total_filtering_method
1465
+ squeeze_attributes
1466
+ capitalize_attributes
1467
+ end
1468
+ end
1469
+ ```
1470
+
1471
+ But to increase readability you should go with the old-fashion way and register
1472
+ each filtering callback method separately.
1473
+
1474
+ ### Data types ###
1475
+
1476
+ The common filters are aware and can operate on attributes that
1477
+ are arrays or hashes. If an array or a hash is detected then
1478
+ the filtering is made **recursively** for each element (or for each value in case
1479
+ of a hash) and the produced structure is returned. If the attribute has an
1480
+ unknown type then its value is not altered at all and left intact.
1481
+
1482
+ Some of the common filters may treat arrays and hashes in a slight different
1483
+ way (e.g. joining and splitting filters do that).
1484
+
1485
+ The common filters are aware of multibyte strings so string
1486
+ operations should handle diacritics properly.
1487
+
1488
+ ### List of filters ###
1489
+
1490
+ * **`capitalize_attributes`**
1491
+ * **`fully_capitalize_attributes`**
1492
+ * **`titleize_attributes`**
1493
+ * **`downcase_attributes`**
1494
+ * **`upcase_attributes`**
1495
+ * **`strip_attributes`**
1496
+ * **`squeeze_attributes`**
1497
+ * **`squish_attributes`**
1498
+
967
1499
  See the
968
1500
  [`ActiveModel::AttributeFilters::Common`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/Common)
969
1501
  for detailed descriptions.
970
1502
 
1503
+ #### Case ####
1504
+
1505
+ * Submodule: [`Case`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/Common/Case.html)
1506
+
1507
+ ##### `capitalize_attributes` #####
1508
+
1509
+ Capitalizes attributes.
1510
+
1511
+ * Callback method: `capitalize_attributes`
1512
+ * Class-level helper: `capitalize_attributes(*attribute_names)`
1513
+ * Uses set: `:should_be_capitalized`
1514
+ * Operates on: strings, arrays of strings, hashes of strings (as values)
1515
+ * Uses annotations: no
1516
+
1517
+ Example:
1518
+
1519
+ ```ruby
1520
+ class User < ActiveRecord::Base
1521
+ include ActiveModel::AttributeFilters::Common::Case
1522
+
1523
+ capitalize_attributes :name
1524
+ before_validation :capitalize_attributes
1525
+ end
1526
+ ```
1527
+
1528
+ or
1529
+
1530
+ ```ruby
1531
+ class User < ActiveRecord::Base
1532
+ include ActiveModel::AttributeFilters::Common::Case
1533
+
1534
+ attributes_that :should_be_capitalized => [ :name ]
1535
+ before_validation :capitalize_attributes
1536
+ end
1537
+ ```
1538
+
1539
+ Then:
1540
+
1541
+ > `"some name"`
1542
+
1543
+ will become:
1544
+
1545
+ > `"Some name"`
1546
+
1547
+ ##### `fully_capitalize_attributes` #####
1548
+
1549
+ Capitalizes attributes and squeezes spaces that separate strings.
1550
+
1551
+ * Callback method: `fully_capitalize_attributes`
1552
+ * Class-level helper: `fully_capitalize_attributes(*attribute_names)`
1553
+ * Uses set: `:should_be_fully_capitalized` and `:should_be_titleized`
1554
+ * Operates on: strings, arrays of strings, hashes of strings (as values)
1555
+ * Uses annotations: no
1556
+
1557
+ Example:
1558
+
1559
+ ```ruby
1560
+ class User < ActiveRecord::Base
1561
+ include ActiveModel::AttributeFilters::Common::Case
1562
+
1563
+ fully_capitalize_attributes :name
1564
+ before_validation :fully_capitalize_attributes
1565
+ end
1566
+ ```
1567
+
1568
+ or
1569
+
1570
+ ```ruby
1571
+ class User < ActiveRecord::Base
1572
+ include ActiveModel::AttributeFilters::Common::Case
1573
+
1574
+ attributes_that :should_be_fully_capitalized => [ :name ]
1575
+ before_validation :fully_capitalize_attributes
1576
+ end
1577
+ ```
1578
+
1579
+ Then:
1580
+
1581
+ > `"some name"`
1582
+
1583
+ will become:
1584
+
1585
+ > `"Some Name"`
1586
+
1587
+ ##### `titleize_attributes` #####
1588
+
1589
+ Titleizes attributes.
1590
+
1591
+ * Callback method: `titleize_attributes`
1592
+ * Class-level helper: `titleize_attributes(*attribute_names)`
1593
+ * Uses set: `:should_be_titleized`
1594
+ * Operates on: strings, arrays of strings, hashes of strings (as values)
1595
+ * Uses annotations: no
1596
+
1597
+ Example:
1598
+
1599
+ ```ruby
1600
+ class User < ActiveRecord::Base
1601
+ include ActiveModel::AttributeFilters::Common::Case
1602
+
1603
+ titleize_attributes :name
1604
+ before_validation :titleize_attributes
1605
+ end
1606
+ ```
1607
+
1608
+ or
1609
+
1610
+ ```ruby
1611
+ class User < ActiveRecord::Base
1612
+ include ActiveModel::AttributeFilters::Common::Case
1613
+
1614
+ attributes_that :should_be_titleized => [ :name ]
1615
+ before_validation :titleize_attributes
1616
+ end
1617
+ ```
1618
+
1619
+ Then:
1620
+
1621
+ > `"some name"`
1622
+
1623
+ will become:
1624
+
1625
+ > `"Some Name"`
1626
+
1627
+ ##### `upcase_attributes` #####
1628
+
1629
+ Upcases attributes.
1630
+
1631
+ * Callback method: `upcase_attributes`
1632
+ * Class-level helper: `upcase_attributes(*attribute_names)`
1633
+ * Uses set: `:should_be_upcased`
1634
+ * Operates on: strings, arrays of strings, hashes of strings (as values)
1635
+ * Uses annotations: no
1636
+
1637
+ Example:
1638
+
1639
+ ```ruby
1640
+ class User < ActiveRecord::Base
1641
+ include ActiveModel::AttributeFilters::Common::Case
1642
+
1643
+ upcase_attributes :name
1644
+ before_validation :upcase_attributes
1645
+ end
1646
+ ```
1647
+
1648
+ or
1649
+
1650
+ ```ruby
1651
+ class User < ActiveRecord::Base
1652
+ include ActiveModel::AttributeFilters::Common::Case
1653
+
1654
+ attributes_that :should_be_upcased => [ :name ]
1655
+ before_validation :upcase_attributes
1656
+ end
1657
+ ```
1658
+
1659
+ Then:
1660
+
1661
+ > `"some name"`
1662
+
1663
+ will become:
1664
+
1665
+ > `"SOME NAME"`
1666
+
1667
+ ##### `downcase_attributes` #####
1668
+
1669
+ Downcases attributes.
1670
+
1671
+ * Callback method: `downcase_attributes`
1672
+ * Class-level helper: `downcase_attributes(*attribute_names)`
1673
+ * Uses set: `:should_be_downcased`
1674
+ * Operates on: strings, arrays of strings, hashes of strings (as values)
1675
+ * Uses annotations: no
1676
+
1677
+ Example:
1678
+
1679
+ ```ruby
1680
+ class User < ActiveRecord::Base
1681
+ include ActiveModel::AttributeFilters::Common::Case
1682
+
1683
+ downcase_attributes :name
1684
+ before_validation :downcase_attributes
1685
+ end
1686
+ ```
1687
+
1688
+ or
1689
+
1690
+ ```ruby
1691
+ class User < ActiveRecord::Base
1692
+ include ActiveModel::AttributeFilters::Common::Case
1693
+
1694
+ attributes_that :should_be_downcased => [ :name ]
1695
+ before_validation :downcase_attributes
1696
+ end
1697
+ ```
1698
+
1699
+ Then:
1700
+
1701
+ > `"SOME NAME"`
1702
+
1703
+ will become:
1704
+
1705
+ > `"some name"`
1706
+
1707
+ #### Strip ####
1708
+
1709
+ * Submodule: [`Strip`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/Common/Strip.html)
1710
+
1711
+ ##### `strip_attributes` #####
1712
+
1713
+ Strips attributes of leading and trailing spaces.
1714
+
1715
+ * Callback method: `strip_attributes`
1716
+ * Class-level helper: `strip_attributes(*attribute_names)`
1717
+ * Uses set: `:should_be_stripped`
1718
+ * Operates on: strings, arrays of strings, hashes of strings (as values)
1719
+ * Uses annotations: no
1720
+
1721
+ Example:
1722
+
1723
+ ```ruby
1724
+ class User < ActiveRecord::Base
1725
+ include ActiveModel::AttributeFilters::Common::Strip
1726
+
1727
+ strip_attributes :name
1728
+ before_validation :strip_attributes
1729
+ end
1730
+ ```
1731
+
1732
+ or
1733
+
1734
+ ```ruby
1735
+ class User < ActiveRecord::Base
1736
+ include ActiveModel::AttributeFilters::Common::Strip
1737
+
1738
+ attributes_that :should_be_stripped => [ :name ]
1739
+ before_validation :strip_attributes
1740
+ end
1741
+ ```
1742
+
1743
+ Then:
1744
+
1745
+ > `" Some Name "`
1746
+
1747
+ will become:
1748
+
1749
+ > `"Some Name"`
1750
+
1751
+ #### Squeeze ####
1752
+
1753
+ * Submodule: [`Squeeze`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/Common/Squeeze.html)
1754
+
1755
+ ##### `squeeze_attributes` #####
1756
+
1757
+ Squeezes attributes (squeezes repeating spaces into one).
1758
+
1759
+ * Callback method: `squeeze_attributes`
1760
+ * Class-level helper: `squeeze_attributes(*attribute_names)`
1761
+ * Uses set: `:should_be_squeezed`
1762
+ * Operates on: strings, arrays of strings, hashes of strings (as values)
1763
+ * Uses annotations: no
1764
+
1765
+ Example:
1766
+
1767
+ ```ruby
1768
+ class User < ActiveRecord::Base
1769
+ include ActiveModel::AttributeFilters::Common::Squeeze
1770
+
1771
+ squeeze_attributes :name
1772
+ before_validation :squeeze_attributes
1773
+ end
1774
+ ```
1775
+
1776
+ or
1777
+
1778
+ ```ruby
1779
+ class User < ActiveRecord::Base
1780
+ include ActiveModel::AttributeFilters::Common::Squeeze
1781
+
1782
+ attributes_that :should_be_squeezed => [ :name ]
1783
+ before_validation :squeeze_attributes
1784
+ end
1785
+ ```
1786
+
1787
+ Then:
1788
+
1789
+ > `"Some Name"`
1790
+
1791
+ will become:
1792
+
1793
+ > `"Some Name"`
1794
+
1795
+ ##### `squish_attributes` #####
1796
+
1797
+ Squishes attributes (removes all whitespace characters on both ends of the string, and then changes remaining consecutive whitespace groups into one space each).
1798
+
1799
+ * Callback method: `squish_attributes`
1800
+ * Class-level helper: `squish_attributes(*attribute_names)`
1801
+ * Uses set: `:should_be_squished`
1802
+ * Operates on: strings, arrays of strings, hashes of strings (as values)
1803
+ * Uses annotations: no
1804
+
1805
+ Example:
1806
+
1807
+ ```ruby
1808
+ class User < ActiveRecord::Base
1809
+ include ActiveModel::AttributeFilters::Common::Squeeze
1810
+
1811
+ squish_attributes :name
1812
+ before_validation :squish_attributes
1813
+ end
1814
+ ```
1815
+
1816
+ or
1817
+
1818
+ ```ruby
1819
+ class User < ActiveRecord::Base
1820
+ include ActiveModel::AttributeFilters::Common::Squeeze
1821
+
1822
+ attributes_that :should_be_squished => [ :name ]
1823
+ before_validation :squish_attributes
1824
+ end
1825
+ ```
1826
+
1827
+ Then:
1828
+
1829
+ > `" Some Name"`
1830
+
1831
+ will become:
1832
+
1833
+ > `"Some Name"`
1834
+
1835
+ #### Split ####
1836
+
1837
+ * Submodule: [`Split`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/Common/Split.html)
1838
+
1839
+ ##### `split_attributes` #####
1840
+
1841
+ Splits attributes into arrays and puts the results into other attributes or into the same attributes.
1842
+
1843
+ * Callback methods: `split_attributes`
1844
+ * Class-level helpers:
1845
+ * `split_attributes(attribute_name, parameters_hash)`
1846
+ * `split_attributes(attribute_name)`
1847
+ * Uses set: `:should_be_splitted`
1848
+ * Operates on: strings, arrays of strings, hashes of strings (as values)
1849
+ * Uses annotations: yes
1850
+ * `split_pattern` - a pattern passed to [`split`](http://www.ruby-doc.org/core/String.html#method-i-split) method (optional)
1851
+ * `split_limit` - a limit passed to `split` method (optional)
1852
+ * `split_into` - attribute names used as destinations for parts
1853
+ * `split_flatten` - flag that causes resulting array to be flattened
1854
+
1855
+ If some source attribute is an array or a hash then the filter will recursively traverse it and
1856
+ operate on each element. The filter works the same way as the `split` method from the `String` class
1857
+ of Ruby's standard library. If the filter encounters an object which is not a string nor an array or a hash,
1858
+ it simply leaves it as is.
1859
+
1860
+ You can set `:pattern` (`:split_pattern`) and `:limit` (`:split_limit`) arguments passed to
1861
+ `split` method but note that **a limit is applied to each processed string separately**,
1862
+ not to the resulting array **(if the processed attribute is an array)**. For instance,
1863
+ if there is a string containing 3 words (`'A B C'`) and the limit is set to 2 then the last two words
1864
+ will be left intact and placed in a second element of the resulting array (`['A', 'B C']`).
1865
+ If the source is an array (`['A', 'B B B B', 'C']`) the result of this operation will be array of arrays
1866
+ (`[ ['A'], ['B B'], ['C'] ]`); as you can see the limit will be applied to its second element.
1867
+
1868
+ If there are no destination attributes defined (`:into` or `:split_into` option) then
1869
+ the resulting array will be written to a current attribute. If there are destination attributes
1870
+ given then the resulting array will be written into them (each subsequent element into each next attribute).
1871
+ The elements that don't fit in the collection are simply ignored.
1872
+
1873
+ There is also `flatten` (or `:split_flatten`) parameter that causes the resulting array to be
1874
+ flattened. Note that it doesn't change how the limits work; they still will be applied but to a single
1875
+ split results, not to the whole resulting array (in case of array of arrays).
1876
+
1877
+ Examples:
1878
+
1879
+ ```ruby
1880
+ class User < ActiveRecord::Base
1881
+ # Including common filter for splitting
1882
+ include ActiveModel::AttributeFilters::Common::Split
1883
+
1884
+ # Registering virtual attribute
1885
+ attr_virtual :real_name
1886
+ attr_accessible :real_name
1887
+
1888
+ # Adding attribute name to :should_be_splitted set
1889
+ split_attributes :real_name
1890
+
1891
+ # Registering callback method
1892
+ # Warning: it will be executed each time model object is validated
1893
+ # (the nice thing is that it allows to validate the results, not the unsplitted data)
1894
+ before_validation :split_attributes
1895
+ end
1896
+ ```
1897
+
1898
+ or without a `split_attributes` helper:
1899
+
1900
+ ```ruby
1901
+ class User < ActiveRecord::Base
1902
+ # Including common filter for splitting
1903
+ include ActiveModel::AttributeFilters::Common::Split
1904
+
1905
+ # Registering virtual attribute
1906
+ attr_virtual :real_name
1907
+ attr_accessible :real_name
1908
+
1909
+ # Adding attribute name to :should_be_splitted set (by hand)
1910
+ attributes_that :should_be_splitted => :real_name
1911
+
1912
+ # Registering callback method
1913
+ before_validation :split_attributes
1914
+ end
1915
+ ```
1916
+
1917
+ The result of executing the filter above will be replacement of a string by an array containing
1918
+ words (each one in a separate element). The `real_name` attribute is a virtual attribute in this example
1919
+ but it could be real attribute. The result will be written as an array into the same attribute since there
1920
+ are no destination attributes given. So `'Paul Wolf'` will become `['Paul', 'Wolf']`.
1921
+
1922
+ Using limit:
1923
+
1924
+ ```ruby
1925
+ class User < ActiveRecord::Base
1926
+ include ActiveModel::AttributeFilters::Common::Split
1927
+
1928
+ attr_virtual :real_name
1929
+ attr_accessible :real_name
1930
+ split_attributes :real_name, :limit => 2
1931
+ before_validation :split_attributes
1932
+ end
1933
+ ```
1934
+
1935
+ or without a `split_attributes` keyword:
1936
+
1937
+ ```ruby
1938
+ class User < ActiveRecord::Base
1939
+ include ActiveModel::AttributeFilters::Common::Split
1940
+
1941
+ attr_virtual :real_name
1942
+ attr_accessible :real_name
1943
+
1944
+ attributes_that :should_be_splitted => { :real_name => { :split_limit => 2 } }
1945
+ before_validation :split_attributes
1946
+ end
1947
+ ```
1948
+
1949
+ The result of the above example will be the same as the previous one with the difference that any
1950
+ reduntant elements will be left intact and placed as the last element of an array. So for data:
1951
+
1952
+ > `'Paul Thomas Wolf'`
1953
+
1954
+ the array will be:
1955
+
1956
+ > `[ 'Paul', 'Thomas Wolf' ]`
1957
+
1958
+ Another example, let's write results to some attributes:
1959
+
1960
+ ```ruby
1961
+ class User < ActiveRecord::Base
1962
+ include ActiveModel::AttributeFilters::Common::Split
1963
+
1964
+ attr_virtual :real_name
1965
+ attr_accessible :real_name
1966
+ split_attributes :real_name, :limit => 2, :into => [ :first_name, :last_name ], :pattern => ' '
1967
+ before_validation :split_attributes
1968
+ end
1969
+ ```
1970
+
1971
+ (The `:pattern` is given here but you may skip it if it's a space.)
1972
+
1973
+ This will split a value of the `real_name` attribute and place the results in the attributes
1974
+ called `first_name` and `last_name`, so for:
1975
+
1976
+ > `'Paul Thomas Wolf'`
1977
+
1978
+ the result will be:
1979
+
1980
+ ```
1981
+ first_name: 'Paul'
1982
+ last_name: 'Thomas Wolf'
1983
+ ```
1984
+
1985
+ If you remove the limit, then it will be quite different:
1986
+
1987
+ ```
1988
+ first_name: 'Paul'
1989
+ last_name: 'Thomas'
1990
+ ```
1991
+
1992
+ That's because there are more results than attributes they fit into. You just have to keep in mind
1993
+ that this filter behaves like the String's split method with the difference when the results are written
1994
+ into other attributes. In that case the limit causes redundant data to be placed in the last element (if a limit
1995
+ is lower or is the same as the count of destination attributes) and its lack causes some of the resulting data to
1996
+ be ignored (if there are more slices than receiving attributes).
1997
+
1998
+ The pattern parameter (`:pattern` when using `split_attributes` or `:split_pattern` when directly
1999
+ annotating attribute in a set) should be a string.
2000
+
2001
+ #### Join ####
2002
+
2003
+ * Submodule: [`Join`](http://rubydoc.info/gems/attribute-filters/ActiveModel/AttributeFilters/Common/Join.html)
2004
+
2005
+ ##### `join_attributes` #####
2006
+
2007
+ Joins attributes and places the results into other attributes or into the same attributes as strings.
2008
+
2009
+ * Callback method: `join_attributes`
2010
+ * Class-level helpers:
2011
+ * `join_attributes(attribute_name, parameters_hash)` (a.k.a `joint_attribute`)
2012
+ * `join_attributes(attribute_name)` (a.k.a `joint_attribute`)
2013
+ * Uses set: `:should_be_joined`
2014
+ * Operates on: strings, arrays of strings
2015
+ * Uses annotations: yes
2016
+ * `join_separator` - a pattern passed to [`join`](http://www.ruby-doc.org/core/Array.html#method-i-join) method (optional)
2017
+ * `join_compact` - compact flag; if true then an array is compacted before it's joined (optional)
2018
+ * `join_from` - attribute names used as sources for joins
2019
+
2020
+ The join filter uses `join` instance method of the `Array` class to produce single string from multiple strings.
2021
+ These strings may be values of other attributes (source attributes), values of an array stored in an attribute
2022
+ or mix of it. If the `:compact` (`:join_compact` in case of manually annotating a set) parameter is given
2023
+ and it's not `false` nor `nil` then results are compacted during processing. That means any slices equals to `nil` are
2024
+ removed.
2025
+
2026
+ If the parameter `:from` (or annotation key `:join_from`) was not given then a currently processed attribute
2027
+ is treated as a source (it should be an array).
2028
+
2029
+ Examples:
2030
+
2031
+ ```ruby
2032
+ class User < ActiveRecord::Base
2033
+ include ActiveModel::AttributeFilters::Common::Join
2034
+
2035
+ attr_virtual :first_name, :last_name
2036
+ attr_accessible :first_name, :last_name
2037
+ join_attributes_into :real_name, :from => [ :first_name, :last_name ]
2038
+ before_validation :join_attributes
2039
+ end
2040
+ ```
2041
+
2042
+ you can also switch source with destination:
2043
+
2044
+ ```ruby
2045
+ join_attributes [ :first_name, :last_name ] => :real_name
2046
+ ```
2047
+
2048
+ or add a descriptive keyword `:into`:
2049
+
2050
+ ```ruby
2051
+ join_attributes [ :first_name, :last_name ], :into => :real_name
2052
+ ```
2053
+
971
2054
  Custom applications
972
2055
  -------------------
973
2056
 
@@ -984,11 +2067,13 @@ class User < ActiveRecord::Base
984
2067
  attributes_that_are required_to_trade: [ :username, :email, :real_name, :address, :account ]
985
2068
 
986
2069
  def is_able_to_trade?
987
- are_attributes_that_are(:required_to_trade).all.present?
2070
+ are_attributes_that_are(:required_to_trade).all.present? and
2071
+ are_attributes_that_are(:required_to_trade).all.valid?
988
2072
  end
989
2073
 
990
2074
  def attributes_missing_to_trade
991
- attributes_that_are(:required_to_trade).list.blank?
2075
+ attributes_that_are(:required_to_trade).list.blank? +
2076
+ attributes_that_are(:required_to_trade).list.invalid?
992
2077
  end
993
2078
  end
994
2079
  ```