normatron 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,345 @@
1
+ # Normatron
2
+
3
+ Normatron is a Ruby On Rails plugin that perform attribute normalizations for ActiveRecord objects.<br />
4
+ With it you can normalize attributes to the desired format before saving them in the database.<br />
5
+ This gem inhibits the work of having to override attributes or create a specific method to perform most of the normalizations.
6
+
7
+ ## Installation
8
+
9
+ Let the bundler install the gem by adding the following into your application gemfile:
10
+
11
+ ```ruby
12
+ gem 'normatron'
13
+ ```
14
+
15
+ And then bundle it up:
16
+
17
+ ```bash
18
+ $ bundle install
19
+ ```
20
+
21
+ Or install it by yourself:
22
+
23
+ ```bash
24
+ $ gem install normatron
25
+ ```
26
+
27
+ Then run the generator:
28
+
29
+ ```bash
30
+ $ rails generator normatron:install
31
+ ```
32
+
33
+ ## The problem
34
+
35
+ Suppose you have a product model as the following:
36
+
37
+ ```ruby
38
+ # ./db/migrate/20120101010000_create_products.rb
39
+ class CreateProducts < ActiveRecord::Migration
40
+ def change
41
+ create_table :products do |t|
42
+ t.string :name
43
+ t.decimal :price, :precision => 10, :scale => 2
44
+ end
45
+ end
46
+ end
47
+ ```
48
+ ```ruby
49
+ # ./app/models/products.rb
50
+ class Product < ActiveRecord::Base
51
+ attr_accessible :name, :price
52
+ end
53
+ ```
54
+
55
+ If you want the *name* attribute be uppercased before saving it into the database, the most usual approaches includes:
56
+
57
+ * Override the *name* setter and convert the value to an uppercased string.
58
+ * Write a method or block and bind it to an [ActiveRecord callback](http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html) (like *before_validation* or *before_save*).
59
+
60
+ Both ways are ilenegant, boring to implement, error prone and very expensive.<br />
61
+ What led me to make this gem and offer a third way to handle this.
62
+
63
+ ## Usage
64
+
65
+ Call the [normalize](http://www.rubydoc.info/github/fernandors87/normatron/Normatron/Extensions/ActiveRecord/ClassMethods:normalize) class method inside your model to set the normalization rules:
66
+
67
+ ```ruby
68
+ # ./app/models/products.rb
69
+ class Product < ActiveRecord::Base
70
+ attr_accessible :name, :price
71
+ normalize :name
72
+ end
73
+ ```
74
+
75
+ And it will behave like this:
76
+
77
+ ```bash
78
+ $ rails console
79
+ > memory = Product.create name: " memory card "
80
+ => #<Product id: nil, name: "memory card", price: nil>
81
+ > unknown = Product.create name: " "
82
+ => #<Product id: nil, name: nil, price: nil>
83
+ ```
84
+
85
+ In this case the `:with` option was ommited, then the [:blank](http://rubydoc.info/gems/normatron/Normatron/Filters/BlankFilter) and [:squish](http://rubydoc.info/gems/normatron/Normatron/Filters/SquishFilter) filters was called to the *name* attribute.<br />
86
+ These are the default filters and can be easily changed in the initializer file.
87
+
88
+ To specify which kind of filters will be binded to each attribute, pass the filter name to the `:with` option:
89
+
90
+ ```ruby
91
+ class MyModel < ActiveRecord::Base
92
+ # Single filter to a single attribute
93
+ normalize :attr_a, :with => :upcase
94
+
95
+ # Multiple filters to multiple attributes
96
+ normalize :attr_b, :attr_c, :attr_d, :with => [:upcase, :squish]
97
+
98
+ # The :keep filter is an example of filter that uses arguments.
99
+ # In this case, the filter is passed as a Hash, where the key is the filter name,
100
+ # and the value is an Array of arguments.
101
+ normalize :attr_e, :with => {:keep => [:Latin, :Z]}
102
+
103
+ # The same as above can be obtained using an Array instead of a Hash.
104
+ # But when filter uses arguments, set him inside another Array is mandatory.
105
+ normalize :attr_f, :with => [[:keep, :Latin, :Z]]
106
+
107
+ # Mix simple filters with filters carrying arguments this way:
108
+ normalize :attr_g, :with => [:blank, {:keep => [:Latin, :Z]}, :squish]
109
+
110
+ # Or this way:
111
+ normalize :attr_h, :with => [:blank, [:keep, :Latin, :Z], :squish]
112
+ end
113
+ ```
114
+
115
+ ### Filter Stackings
116
+
117
+ The normalize method stack the filters when called multiple times to the same attribute.
118
+
119
+ ```ruby
120
+ # 1st Way: Without stacking filters
121
+ class MyModel < ActiveRecord::Base
122
+ normalize :attr_a, :with => :blank
123
+ normalize :attr_b, :with => [:blank, :squish]
124
+ normalize :attr_c, :with => [:blank, :squish, :upcase]
125
+ end
126
+
127
+ # 2nd Way: Stacking filters
128
+ # This piece of code produces the exactly the same results as 1st way.
129
+ class MyModel < ActiveRecord::Base
130
+ normalize :attr_a, :attr_b, :attr_c, :with => :blank
131
+ normalize :attr_b, :attr_c, :with => :squish
132
+ normalize :attr_c, :with => :upcase
133
+ end
134
+ ```
135
+
136
+ ### Typing Less
137
+
138
+ In some cases is possible to write much less by passing multiple attributes to normalize method:
139
+
140
+ ```ruby
141
+ # 1st Way: Setting rules to single attributes
142
+ class MyModel < ActiveRecord::Base
143
+ normalize :attr_a, :with => :blank
144
+ normalize :attr_b, :with => :squish
145
+ normalize :attr_c, :with => :upcase
146
+ normalize :attr_d, :with => [:blank, :squish]
147
+ normalize :attr_e, :with => [:blank, :upcase]
148
+ normalize :attr_f, :with => [:squish, :upcase]
149
+ normalize :attr_g, :with => [:blank, :squish, :upcase]
150
+ end
151
+
152
+ # 2nd Way: Setting rules to multiple attributes
153
+ class MyModel < ActiveRecord::Base
154
+ normalize :attr_a, :attr_d, :attr_e, :attr_g, :with => :blank
155
+ normalize :attr_b, :attr_d, :attr_f, :attr_g, :with => :squish
156
+ normalize :attr_c, :attr_e, :attr_f, :attr_g, :with => :upcase
157
+ end
158
+ ```
159
+
160
+ ### Filters
161
+
162
+ Normatron have a bunch of built-in filters.<br />
163
+ The list of all filters and instructions of how to use them can be found in the [Normatron::Filters](http://rubydoc.info/gems/normatron/Normatron/Filters) documentation.
164
+
165
+ ### Getting Normalization Rules
166
+
167
+ You can know what kind of rules was set to a model as following:
168
+
169
+ ```
170
+ $ rails console
171
+ > User.normalize_rules
172
+ => {:login => {:blank => nil, :remove => [:Zs]},
173
+ :email => {:blank => nil, :squish => nil, :downcase => nil},
174
+ :name => {:blank => nil, :squish => nil, :upcase => nil}}
175
+ ```
176
+
177
+ ### Applying Normalizations
178
+
179
+ All attributes are automatically normalized by [before_validation](http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html) callback, what means any method that evoke the *before_validation* callback will perform the normalizations.<br />
180
+ Some of these methods includes:
181
+
182
+ * [ActiveRecord::Validations#valid?](http://api.rubyonrails.org/classes/ActiveRecord/Validations.html#method-i-valid-3F)
183
+ * [ActiveRecord::Persistence#save](http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-save)
184
+ * [ActiveRecord::Persistence::ClassMethods#create](http://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-create)
185
+
186
+ To apply the normalizations without doing validations or persistence, just call the [apply_normalizations](http://rubydoc.info/gems/normatron/Normatron/Extensions/ActiveRecord/InstanceMethods#apply_normalizations-instance_method) method as following:
187
+
188
+ ```
189
+ $ rails console
190
+ > user = User.new
191
+ > user.name = " anon "
192
+ > user.email = "ANON@GMAIL.COM"
193
+ > user.login = "my name \n is anon"
194
+ > user.attributes
195
+ => {:id => nil,
196
+ :name => " anon ",
197
+ :email => "ANON@GMAIL.COM",
198
+ :login => "my name \n is anon"}
199
+ > user.apply_normalizations
200
+ > user.attributes
201
+ => {:id => nil,
202
+ :name => "ANON",
203
+ :email => "anon@gmail.com",
204
+ :login => "mynameisanon"}
205
+ ```
206
+
207
+ ### Building Your Own Filters
208
+
209
+ #### 1st way: Defining a filter as a module
210
+
211
+ Create a new module with the following characteristics:
212
+
213
+ * Having a module method called evaluate
214
+ * The evaluate method must receive at least one argument
215
+ * The first argument must be the value to be filtered
216
+
217
+ Here is an example:
218
+
219
+ ```ruby
220
+ # ./lib/my_filters/emoticon_filter.rb
221
+ module MyFilters
222
+ module EmoticonFilter
223
+ def self.evaluate(value, type)
224
+ emot = (type == :sad) ? ":(" : ":D"
225
+ value + emot
226
+ end
227
+ end
228
+ end
229
+ ```
230
+
231
+ Then add him to your Normatron initializer file:
232
+
233
+ ```ruby
234
+ require 'lib/my_filters/emoticon_filter'
235
+ Normatron.setup do |config|
236
+ #...
237
+ config.filters[:emoticon] = MyFilter::EmoticonFilter
238
+ #...
239
+ end
240
+ ```
241
+
242
+ Usage:
243
+
244
+ ```ruby
245
+ class Message < ActiveRecord::Base
246
+ belongs_to :person
247
+ normalize :content, :with => {:emoticon => :happy} # Always happy
248
+ end
249
+ ```
250
+
251
+ 1. Pros
252
+ * Allow create specific documentation for your filter
253
+ * More easy to make them portable for multiple applications and purposes
254
+ 2. Cons
255
+ * Verbose
256
+
257
+ #### 2nd way: Defining a filter as a lambda
258
+
259
+ Create a new lambda object with the following characteristics:
260
+
261
+ * Must receive at least one argument
262
+ * The first argument must be the value to be filtered
263
+
264
+ The lambda will be defined inside Normatron initializer:
265
+
266
+ ```ruby
267
+ Normatron.setup do |config|
268
+ #...
269
+
270
+ config.filters[:emoticon] = lambda do |value, type|
271
+ emot = (type == :sad) ? ":(" : ":D"
272
+ value + emot
273
+ end
274
+
275
+ #...
276
+ end
277
+ ```
278
+
279
+ Usage:
280
+
281
+ ```ruby
282
+ class Message < ActiveRecord::Base
283
+ belongs_to :person
284
+ normalize :content, :with => {:emoticon => :sad} # Always sad
285
+ end
286
+ ```
287
+
288
+ 1. Pros
289
+ * Less verbose than 1st method
290
+ 2. Cons
291
+ * Hard to share filter functionalities between other applications
292
+ * Hard to use filter functionalities for other purposes
293
+
294
+ #### 3th way: Defining a filter as model instance method
295
+
296
+ Create a new instance method within your model with the following characteristics:
297
+
298
+ * Must receive at least one argument
299
+ * The first argument must be the value to be filtered
300
+
301
+ The method will be defined inside your model class:
302
+
303
+ ```ruby
304
+ class Message < ActiveRecord::Base
305
+ belongs_to :person
306
+ normalize :content, :with => :emoticon # Happy or sad according person's mood
307
+
308
+ def emoticon(value)
309
+ emot = (person.mood == :happy) ? ":D" : ":("
310
+ value + emot
311
+ end
312
+ end
313
+ ```
314
+
315
+ 1. Pros
316
+ * Can use instance variables
317
+ 2. Cons
318
+ * Cannot be shared between objects
319
+
320
+ # Contributing
321
+
322
+ There are several ways to make this gem even better:
323
+
324
+ * Forking this project
325
+ * Adding new features or bug fixes
326
+ * Making tests
327
+ * Commiting your changes
328
+ * Reporting any bug or unexpected behavior
329
+ * Suggesting any improvement
330
+ * Sharing with your friends, forums, communities, job, etc...
331
+ * Helping users with difficulty using this gem
332
+ * Paying me a beer =]
333
+
334
+ # Credits
335
+
336
+ This gem was initially inspired on:
337
+
338
+ * [normalize_attributes](https://github.com/fnando/normalize_attributes) - I liked the cleaner code and simplicity of this gem.
339
+ * [attribute_normalizer](https://github.com/mdeering/attribute_normalizer) - Very powerful.
340
+
341
+ The idea is to mix the good things of both gems, adding some features and changing something to fit my taste.
342
+
343
+ # License
344
+
345
+ See file attached to source code or [click here](https://github.com/fernandors87/normatron/blob/master/MIT-LICENSE).
@@ -2,19 +2,25 @@ module Normatron
2
2
  ##
3
3
  # Creates all necessary files to run Normatron in your Ruby On Rails project.
4
4
  #
5
- # These files are:
5
+ # Even when required, Normatron doesn't include his functionalities into your ORM automatically.
6
+ # It behaves this way to avoid some potential issues.
7
+ #
8
+ # Using this generator, you can get fully control of Normatron behavior through initialization file.
9
+ #
10
+ # These files includes:
6
11
  # * config/initializers/normatron.rb
7
12
  #
8
- # *Usage:*
9
- # $ rails generator normatron:install
13
+ # h2. Usage
14
+ #
15
+ # pre. $ rails generator normatron:install
10
16
  class InstallGenerator < Rails::Generators::Base
11
17
  source_root File.expand_path('../templates', __FILE__)
12
18
  desc "Create all necessary files to run Normatron in your Ruby On Rails project."
13
19
 
14
20
  ##
15
- # Copy files from templates to their respective destination
21
+ # Copy files from templates to their respective destination.
16
22
  #
17
- # These files are:
23
+ # These files includes:
18
24
  # * config/initializers/normatron.rb
19
25
  def copy_files
20
26
  copy_file "normatron.rb", "config/initializers/normatron.rb"
@@ -1,4 +1,21 @@
1
1
  Normatron.setup do |config|
2
- config.default_filters = :blank, :squish
2
+ # Comment the line below to disable Normatron
3
3
  config.add_orm Normatron::Extensions::ActiveRecord
4
+
5
+ # Create your own filters using a Proc/lambda as following:
6
+ #
7
+ # config.filters[:smile] = lambda do |value|
8
+ # value.kind_of?(String) : value + " =]" : value
9
+ # end
10
+
11
+ # Include your own filter module as following:
12
+ #
13
+ # config.filters[:laugh] = YourNamespace::YourFilterSet::YourFilter
14
+
15
+ # Set the default filters.
16
+ #
17
+ # Examples:
18
+ # config.default_filters = :upcase, {:keep => [:L, :N]}
19
+ # config.default_filters = :ascii, [:keep, :L, :N]
20
+ config.default_filters = :blank, :squish
4
21
  end
@@ -9,6 +9,7 @@ module Normatron
9
9
  @default_filters = { blank: nil, squish: nil }
10
10
 
11
11
  @filters = {}
12
+ @filters[:ascii] = Normatron::Filters::AsciiFilter
12
13
  @filters[:blank] = Normatron::Filters::BlankFilter
13
14
  @filters[:camelize] = Normatron::Filters::CamelizeFilter
14
15
  @filters[:capitalize] = Normatron::Filters::CapitalizeFilter
@@ -13,30 +13,32 @@ module Normatron
13
13
  before_validation :apply_normalizations
14
14
 
15
15
  class << self
16
- attr_accessor :normalize_filters
16
+ attr_accessor :normalize_rules
17
+ alias :normalize_filters :normalize_rules
18
+ alias :normalize_filters= :normalize_rules=
17
19
  end
18
20
  end
19
21
  end
20
22
 
21
23
  module ClassMethods
22
24
  def normalize(*args)
23
- # Check existence of all attributes
25
+ # Check the existence of all attributes
24
26
  options = args.extract_options!
25
27
  filters, columns = args.map(&:to_s), column_names
26
28
  raise UnknownAttributeError if (columns & filters).size != filters.size
27
29
 
28
- # Specify the use of default filters or not
30
+ # Need to use default filters?
29
31
  if options[:with].nil? || options[:with].blank?
30
32
  new_filters = Normatron.config.default_filters
31
33
  else
32
34
  new_filters = Normatron.build_hash(options[:with])
33
35
  end
34
36
 
35
- # Append to older filters hash
36
- @normalize_filters ||= {}
37
- @normalize_filters =
38
- args.reduce(@normalize_filters) do |hash, att|
39
- filters = (@normalize_filters[att] || {}).merge(new_filters)
37
+ # Append new filters to rules
38
+ @normalize_rules ||= {}
39
+ @normalize_rules =
40
+ args.reduce(@normalize_rules) do |hash, att|
41
+ filters = (@normalize_rules[att] || {}).merge(new_filters)
40
42
  hash.merge({att => filters})
41
43
  end
42
44
  end
@@ -44,7 +46,7 @@ module Normatron
44
46
 
45
47
  module InstanceMethods
46
48
  def apply_normalizations
47
- named_filters = Normatron.configuration.filters
49
+ listed_filters = Normatron.configuration.filters
48
50
 
49
51
  self.class.normalize_filters.each do |attribute, filters|
50
52
  value = send("#{attribute}_before_type_cast") || send(attribute)
@@ -52,8 +54,10 @@ module Normatron
52
54
  filters.each do |filter, args|
53
55
  if self.respond_to? filter
54
56
  value = send(filter, value, *args)
55
- elsif !named_filters[filter].nil?
56
- value = named_filters[filter].evaluate(value, *args)
57
+ elsif listed_filters[filter].kind_of? Module
58
+ value = listed_filters[filter].evaluate(value, *args)
59
+ elsif listed_filters[filter].kind_of? Proc
60
+ value = listed_filters[filter].call(value, *args)
57
61
  else
58
62
  raise UnknownFilterError
59
63
  end
@@ -1,31 +1,37 @@
1
- require 'normatron/filters/blank_filter'
2
- require 'normatron/filters/camelize_filter'
3
- require 'normatron/filters/capitalize_filter'
4
- require 'normatron/filters/chomp_filter'
5
- require 'normatron/filters/dasherize_filter'
6
- require 'normatron/filters/downcase_filter'
7
- require 'normatron/filters/dump_filter'
8
- require 'normatron/filters/keep_filter'
9
- require 'normatron/filters/remove_filter'
10
- require 'normatron/filters/squeeze_filter'
11
- require 'normatron/filters/squish_filter'
12
- require 'normatron/filters/strip_filter'
13
- require 'normatron/filters/swapcase_filter'
14
- require 'normatron/filters/titleize_filter'
15
- require 'normatron/filters/underscore_filter'
16
- require 'normatron/filters/upcase_filter'
1
+ Dir[File.dirname(__FILE__) + "/filters/*_filter.rb"].each do |file|
2
+ require file
3
+ end
17
4
 
18
5
  module Normatron
19
- # Top-Level namespace of all native Normatron filters.
20
- #
6
+
7
+ # Top-Level namespace of all built-in Normatron filters.
8
+ #
21
9
  # All filters share some characteristics:
22
10
  # * They have the <code>Filter</code> suffix in the name.
23
11
  # * Has a class method called <code>evaluate</code>, which runs what the filter claims to do.
24
12
  # * The first argument of the method <code>evaluate</code> always will be the variable to be filtered.
25
- # * They returns a different object from the input variable, ie, even if the value remains unchanged, the <code>object_id</code> of both variables will be different.
26
- # * They treat accented characters and not just <code>/[a-zA-Z]/</code> interval.
13
+ # * They returns a different object from the input variable, i.e., even if object value remains unchanged, the <code>object_id</code> will be different.
14
+ # * They treat unicode characters(<code>/\p{Ll}\p{Lu}/u</code>) instead of only ASCII characters(<code>/[a-zA-Z]/</code>).
15
+ #
16
+ # table{font-family: monospace; font-size: 90%}.
17
+ # |_. CLASS |_. SYMBOL |_. SHORT DESCRIPTION |
18
+ # |"AsciiFilter":./Filters/AsciiFilter |:ascii |Converts Unicode(and accented ASCII) characters to their plain-text ASCII equivalents.|
19
+ # |"BlankFilter":./Filters/BlankFilter |:blank |Returns nil for a blank string or the string itself otherwise. |
20
+ # |"CamelizeFilter":./Filters/CamelizeFilter |:camelize |Convert string to UpperCamelCase or lowerCamelCase. |
21
+ # |"CapitalizeFilter":./Filters/CapitalizeFilter|:capitalize|Makes only the first character as capital letter. |
22
+ # |"ChompFilter":./Filters/ChompFilter |:chomp |Remove the given record separator from the end of the string. |
23
+ # |"DasherizeFilter":./Filters/DasherizeFilter |:dasherize |Replaces all underscores with dashes. |
24
+ # |"DowncaseFilter":./Filters/DowncaseFilter |:downcase |Lowercase all characters. |
25
+ # |"DumpFilter":./Filters/DumpFilter |:dump |Creates a literal string representation. |
26
+ # |"KeepFilter":./Filters/KeepFilter |:keep |Remove the characters that doesn't match the given properties. |
27
+ # |"RemoveFilter":./Filters/RemoveFilter |:remove |Remove the characters that match the given properties. |
28
+ # |"SqueezeFilter":./Filters/SqueezeFilter |:squeeze |Remove multiple occurences of the same character. |
29
+ # |"SquishFilter":./Filters/SquishFilter |:squish |Strips the input, remove line-breaks and multiple spaces. |
30
+ # |"SwapcaseFilter":./Filters/SwapcaseFilter |:swapcase |Replaces uppercased characters by lowercased and vice versa. |
31
+ # |"TitleizeFilter":./Filters/TitleizeFilter |:titleize |Capitalizes the first character of each word. |
32
+ # |"UnderscoreFilter":./Filters/UnderscoreFilter|:underscore|Makes an underscored lowercase form from the expression in the string. |
33
+ # |"UpcaseFilter":./Filters/UpcaseFilter |:upcase |Uppercase all characters. |
27
34
  module Filters
28
35
  end
29
36
  end
30
37
 
31
-
@@ -0,0 +1,26 @@
1
+ require 'stringex/unidecoder'
2
+
3
+ module Normatron
4
+ module Filters
5
+ module AsciiFilter
6
+
7
+ ##
8
+ # Converts Unicode(and accented ASCII) characters to their plain-text ASCII equivalents.
9
+ #
10
+ # @example
11
+ # AsciiFilter.evaluate("EVOLUÇÃO") #=> "EVOLUCAO"
12
+ # AsciiFilter.evaluate("⠋⠗⠁⠝⠉⠑") #=> "france"
13
+ #
14
+ # @example Using as ActiveRecord::Base normalizer
15
+ # normalize :attribute_a, :with => :ascii
16
+ # normalize :attribute_b, :with => [:custom_filter, :ascii]
17
+ #
18
+ # @param [String] input A character sequence
19
+ # @return [String] The transliterated character sequence or the object itself
20
+ # @see http://rubydoc.info/gems/stringex/Stringex/Unidecoder Stringex::Unidecoder
21
+ def self.evaluate(input, *args)
22
+ input.kind_of?(String) ? Stringex::Unidecoder.decode(input) : input
23
+ end
24
+ end
25
+ end
26
+ end
@@ -14,16 +14,6 @@ module Normatron
14
14
  value.gsub(regex, "")
15
15
  end
16
16
 
17
- def evaluate_strip(value, edges)
18
- constructs = []
19
- constructs << '\A\s*' if edges == :L || edges == :LR
20
- constructs << '\s*\z' if edges == :R || edges == :LR
21
- regex_string = constructs.join '|'
22
- regex = Regexp.new(/#{regex_string}/)
23
-
24
- value.gsub(regex, '')
25
- end
26
-
27
17
  def acronyms
28
18
  inflections.acronyms
29
19
  end
@@ -22,11 +22,23 @@ module Normatron
22
22
  # normalize :attribute_e, :with => [:custom_filter, {:strip => :R}]
23
23
  #
24
24
  # @param [String] input A character sequence
25
- # @param [Symbol] option :L to strip trailing spaces, :R for leading spaces and :LR for both
25
+ # @param [Symbol] edges :L to strip trailing spaces, :R for leading spaces and :LR for both
26
26
  # @return [String] The character sequence without trailing and leading spaces or the object itself
27
27
  # @see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-strip String#strip
28
- def self.evaluate(input, option=:LR)
29
- input.kind_of?(String) ? evaluate_strip(input, option) : input
28
+ def self.evaluate(input, edges=:LR)
29
+ return input unless input.kind_of?(String)
30
+
31
+ regex_string =
32
+ case edges
33
+ when :L
34
+ '\A\s*'
35
+ when :R
36
+ '\s*\z'
37
+ when :LR
38
+ '\A\s*|\s*\z'
39
+ end
40
+ regex = Regexp.new(/#{regex_string}/)
41
+ input.gsub(regex, '')
30
42
  end
31
43
  end
32
44
  end
@@ -9,14 +9,14 @@ module Normatron
9
9
  # Uppercase all characters.
10
10
  #
11
11
  # @example
12
- # upcase("borderlands") #=> "BORDERLANDS"
12
+ # UpcaseFilter.evaluate("borderlands") #=> "BORDERLANDS"
13
13
  #
14
14
  # @example Using as ActiveRecord::Base normalizer
15
15
  # normalize :attribute_a, :with => :upcase
16
16
  # normalize :attribute_b, :with => [:custom_filter, :upcase]
17
17
  #
18
- # @return [String, Chars] The uppercased character sequence or the object itself
19
- # @see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-upcase String#upcase
18
+ # @param [String] input A character sequence
19
+ # @return [String] The uppercased character sequence or the object itself
20
20
  # @see http://api.rubyonrails.org/classes/ActiveSupport/Multibyte/Chars.html#method-i-upcase ActiveSupport::Multibyte::Chars#upcase
21
21
  # @see DownFilter Normatron::Filters::DownFilter
22
22
  # @see TitleizeFilter Normatron::Filters::TitleizeFilter
@@ -1,3 +1,3 @@
1
1
  module Normatron
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -21,28 +21,37 @@ describe Normatron::Configuration do
21
21
  end
22
22
 
23
23
  describe :filters do
24
- it "should be initialized" do
25
- subject.filters[:blank] .should eq Normatron::Filters::BlankFilter
26
- subject.filters[:camelize] .should eq Normatron::Filters::CamelizeFilter
27
- subject.filters[:capitalize].should eq Normatron::Filters::CapitalizeFilter
28
- subject.filters[:chomp] .should eq Normatron::Filters::ChompFilter
29
- subject.filters[:dasherize] .should eq Normatron::Filters::DasherizeFilter
30
- subject.filters[:downcase] .should eq Normatron::Filters::DowncaseFilter
31
- subject.filters[:dump] .should eq Normatron::Filters::DumpFilter
32
- subject.filters[:keep] .should eq Normatron::Filters::KeepFilter
33
- subject.filters[:remove] .should eq Normatron::Filters::RemoveFilter
34
- subject.filters[:squeeze] .should eq Normatron::Filters::SqueezeFilter
35
- subject.filters[:squish] .should eq Normatron::Filters::SquishFilter
36
- subject.filters[:strip] .should eq Normatron::Filters::StripFilter
37
- subject.filters[:swapcase] .should eq Normatron::Filters::SwapcaseFilter
38
- subject.filters[:titleize] .should eq Normatron::Filters::TitleizeFilter
39
- subject.filters[:underscore].should eq Normatron::Filters::UnderscoreFilter
40
- subject.filters[:upcase] .should eq Normatron::Filters::UpcaseFilter
24
+ context "when initialized" do
25
+ it { subject.filters[:ascii] .should eq Normatron::Filters::AsciiFilter }
26
+ it { subject.filters[:blank] .should eq Normatron::Filters::BlankFilter }
27
+ it { subject.filters[:camelize] .should eq Normatron::Filters::CamelizeFilter }
28
+ it { subject.filters[:capitalize].should eq Normatron::Filters::CapitalizeFilter }
29
+ it { subject.filters[:chomp] .should eq Normatron::Filters::ChompFilter }
30
+ it { subject.filters[:dasherize] .should eq Normatron::Filters::DasherizeFilter }
31
+ it { subject.filters[:downcase] .should eq Normatron::Filters::DowncaseFilter }
32
+ it { subject.filters[:dump] .should eq Normatron::Filters::DumpFilter }
33
+ it { subject.filters[:keep] .should eq Normatron::Filters::KeepFilter }
34
+ it { subject.filters[:remove] .should eq Normatron::Filters::RemoveFilter }
35
+ it { subject.filters[:squeeze] .should eq Normatron::Filters::SqueezeFilter }
36
+ it { subject.filters[:squish] .should eq Normatron::Filters::SquishFilter }
37
+ it { subject.filters[:strip] .should eq Normatron::Filters::StripFilter }
38
+ it { subject.filters[:swapcase] .should eq Normatron::Filters::SwapcaseFilter }
39
+ it { subject.filters[:titleize] .should eq Normatron::Filters::TitleizeFilter }
40
+ it { subject.filters[:underscore].should eq Normatron::Filters::UnderscoreFilter }
41
+ it { subject.filters[:upcase] .should eq Normatron::Filters::UpcaseFilter }
41
42
  end
42
43
 
43
- it "should allow add new filters" do
44
- subject.filters[:smile] = MyFilters::SmileFilter
45
- subject.filters[:smile].should eq MyFilters::SmileFilter
44
+ context "when new filter is added" do
45
+ it "module filter should be set" do
46
+ subject.filters[:smile] = MyFilters::SmileFilter
47
+ subject.filters[:smile].should eq MyFilters::SmileFilter
48
+ end
49
+
50
+ it "lambda filter should be set" do
51
+ lambda_filter = lambda { |input, value| input << value }
52
+ subject.filters[:append] = lambda_filter
53
+ subject.filters[:append].should eq lambda_filter
54
+ end
46
55
  end
47
56
 
48
57
  it "should allow remove filters" do
@@ -10,13 +10,12 @@ describe Normatron::Extensions::ActiveRecord do
10
10
  let(:model) { User }
11
11
 
12
12
  before(:all) { ActiveRecord::Base.send(:include, Normatron::Extensions::ActiveRecord) }
13
- before(:each) { model.normalize_filters = nil }
13
+ before(:each) { model.normalize_rules = nil }
14
14
  after(:all) do
15
15
  ActiveRecord.send(:remove_const, :Base)
16
16
  load 'active_record/base.rb'
17
17
  end
18
18
 
19
-
20
19
  describe :normalize do
21
20
  subject { model }
22
21
 
@@ -28,31 +27,31 @@ describe Normatron::Extensions::ActiveRecord do
28
27
 
29
28
  it "should append default filters" do
30
29
  subject.normalize :login, :email
31
- subject.normalize_filters.should == { :login => configuration.default_filters,
30
+ subject.normalize_rules.should == { :login => configuration.default_filters,
32
31
  :email => configuration.default_filters }
33
32
  end
34
33
 
35
34
  it "should stack filters for multiple calls" do
36
35
  subject.normalize :login
37
36
  subject.normalize :login, :with => :upcase
38
- subject.normalize_filters.should == { :login => configuration.default_filters.merge({ :upcase => nil }) }
37
+ subject.normalize_rules.should == { :login => configuration.default_filters.merge({ :upcase => nil }) }
39
38
  end
40
39
 
41
40
  it "should append multiple attributes" do
42
41
  subject.normalize :login, :email, :phone
43
- subject.normalize_filters.should == { login: { squish: nil, blank: nil },
42
+ subject.normalize_rules.should == { login: { squish: nil, blank: nil },
44
43
  email: { squish: nil, blank: nil },
45
44
  phone: { squish: nil, blank: nil } }
46
45
  end
47
46
 
48
47
  it "should allow multiple filters" do
49
48
  subject.normalize :login, :with => [:upcase, :blank]
50
- subject.normalize_filters.should == { login: { upcase: nil, blank: nil } }
49
+ subject.normalize_rules.should == { login: { upcase: nil, blank: nil } }
51
50
  end
52
51
 
53
52
  it "should allow multiple filters" do
54
53
  subject.normalize :login, :with => [[:keep, :L], { :remove => [:N] }]
55
- subject.normalize_filters.should == { login: { keep: [:L], remove: [:N] } }
54
+ subject.normalize_rules.should == { login: { keep: [:L], remove: [:N] } }
56
55
  end
57
56
  end
58
57
 
@@ -73,6 +72,22 @@ describe Normatron::Extensions::ActiveRecord do
73
72
  subject.login.should eq "... =("
74
73
  end
75
74
 
75
+ it "should run module filter" do
76
+ Normatron.configuration.filters[:smile] = MyFilters::SmileFilter
77
+ model.normalize :login, :with => :smile
78
+ subject.login = "hello!"
79
+ subject.apply_normalizations
80
+ subject.login.should eq "hello! =]"
81
+ end
82
+
83
+ it "should run lambda filter" do
84
+ Normatron.configuration.filters[:dots] = lambda { |value| value + "..."}
85
+ model.normalize :login, :with => :dots
86
+ subject.login = "word"
87
+ subject.apply_normalizations
88
+ subject.login.should eq "word..."
89
+ end
90
+
76
91
  it "should run native filter" do
77
92
  model.normalize :login, :with => :squish
78
93
  subject.login = " word "
@@ -0,0 +1,14 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+ require 'normatron/filters/ascii_filter'
5
+
6
+ describe Normatron::Filters::AsciiFilter do
7
+ it_should_behave_like "string processor"
8
+ it_should_behave_like "evaluable filter", ["ÉBRIO" ], "EBRIO"
9
+ it_should_behave_like "evaluable filter", ["até" ], "ate"
10
+ it_should_behave_like "evaluable filter", ["cirurgião" ], "cirurgiao"
11
+ it_should_behave_like "evaluable filter", ["email@domain.com" ], "email@domain.com"
12
+ it_should_behave_like "evaluable filter", ["éçü&! *¬¬" ], "ecu&! *!!"
13
+ it_should_behave_like "evaluable filter", ["⠋⠗⠁⠝⠉⠑" ], "france"
14
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: normatron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-10 00:00:00.000000000 Z
12
+ date: 2012-10-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -43,6 +43,22 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: 3.2.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: stringex
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.4.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.4.0
46
62
  - !ruby/object:Gem::Dependency
47
63
  name: sqlite3
48
64
  requirement: !ruby/object:Gem::Requirement
@@ -106,13 +122,14 @@ files:
106
122
  - lib/normatron/filters/helpers.rb
107
123
  - lib/normatron/filters/downcase_filter.rb
108
124
  - lib/normatron/filters/chomp_filter.rb
125
+ - lib/normatron/filters/ascii_filter.rb
109
126
  - lib/normatron/extensions.rb
110
127
  - lib/normatron/version.rb
111
128
  - lib/normatron/extensions/active_record.rb
112
129
  - lib/normatron.rb
113
130
  - MIT-LICENSE
114
131
  - Rakefile
115
- - README.textile
132
+ - README.md
116
133
  - spec/spec_helper.rb
117
134
  - spec/support/user_model.rb
118
135
  - spec/support/my_filters.rb
@@ -123,6 +140,7 @@ files:
123
140
  - spec/normatron/filters/chomp_filter_spec.rb
124
141
  - spec/normatron/filters/capitalize_filter_spec.rb
125
142
  - spec/normatron/filters/upcase_filter_spec.rb
143
+ - spec/normatron/filters/ascii_filter_spec.rb
126
144
  - spec/normatron/filters/strip_filter_spec.rb
127
145
  - spec/normatron/filters/underscore_filter_spec.rb
128
146
  - spec/normatron/filters/keep_filter_spec.rb
@@ -170,6 +188,7 @@ test_files:
170
188
  - spec/normatron/filters/chomp_filter_spec.rb
171
189
  - spec/normatron/filters/capitalize_filter_spec.rb
172
190
  - spec/normatron/filters/upcase_filter_spec.rb
191
+ - spec/normatron/filters/ascii_filter_spec.rb
173
192
  - spec/normatron/filters/strip_filter_spec.rb
174
193
  - spec/normatron/filters/underscore_filter_spec.rb
175
194
  - spec/normatron/filters/keep_filter_spec.rb
@@ -1,200 +0,0 @@
1
- h1. Normatron
2
-
3
- Normatron is a Ruby On Rails plugin that perform attribute normalizations for ActiveRecord objects.
4
- With it you can normalize attributes to the desired format before saving them in the database.
5
- This gem inhibits the work of having to override attributes or create a specific method to perform most of the normalizations.
6
-
7
- h2. Installation
8
-
9
- Let the bundler install the gem by adding the following into your application gemfile:
10
-
11
- pre. gem 'normatron'
12
-
13
- And then bundle it up:
14
-
15
- pre. $ bundle install
16
-
17
- Or install it by yourself:
18
-
19
- pre. $ gem install normatron
20
-
21
- Then run the generator:
22
-
23
- pre. $ rails generator normatron:install
24
-
25
- h2. The problem
26
-
27
- Suppose you have a product model as the following:
28
-
29
- pre. # ./db/migrate/20120101010000_create_products.rb
30
- class CreateProducts < ActiveRecord::Migration
31
- def change
32
- create_table :products do |t|
33
- t.string :name
34
- t.decimal :price, :precision => 10, :scale => 2
35
- end
36
- end
37
- end
38
-
39
- pre. # ./app/models/products.rb
40
- class Product < ActiveRecord::Base
41
- attr_accessible :name, :price
42
- end
43
-
44
- And we want the _name_ attribute be uppercased before saving it into the database.
45
- The most usual approach to do this includes:
46
-
47
- * Override the _name_ setter and convert the value to an uppercased string.
48
- * Write a method or block and bind it to the "before_validation":http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html callback.
49
-
50
- Both ways are ilenegant, boring, error prone and very expensive.
51
- What led me to make this gem and offer a third way to solve this issue:
52
-
53
- h2. Usage
54
-
55
- Normatron uses ":squish":http://rubydoc.info/gems/normatron/Normatron/Filters/SquishFilter and ":blank":http://rubydoc.info/gems/normatron/Normatron/Filters/BlankFilter as default filters.
56
- These filters are applied to all attributes through "normalize":http://rubydoc.info/gems/normatron/Normatron/Extensions/ActiveRecord/ClassMethods#normalize-instance_method function, since no options is given.
57
-
58
- pre. # ./app/models/products.rb
59
- class Product < ActiveRecord::Base
60
- attr_accessible :name, :price
61
- normalize :name
62
- end
63
-
64
- pre. $ rails console
65
- > memory = Product.create name: " memory card "
66
- => #<Product id: nil, name: "memory card", price: nil>
67
- > null = Product.create name: " "
68
- => #<Product id: nil, name: nil, price: nil>
69
-
70
- h3. Configurations
71
-
72
- "normatron:install":http://rubydoc.info/gems/normatron/Normatron/InstallGenerator generator creates a configuration file located at @./config/initializers/normatron.rb@.
73
-
74
- For now, you can set the default normalization filters or disable normatron by commenting the @add_orm@ line.
75
-
76
- h3. Applying normalizations
77
-
78
- Methods like "create":http://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-create, "valid?":http://api.rubyonrails.org/classes/ActiveRecord/Validations.html#method-i-valid-3F or "save":http://api.rubyonrails.org/classes/ActiveRecord/Validations.html#method-i-valid-3F always call the "apply_normalizations":http://rubydoc.info/gems/normatron/Normatron/Extensions/ActiveRecord/InstanceMethods#apply_normalizations-instance_method method, thought "before_validation":http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html callback.
79
- This is the method that invokes all filters to their attributes. So you can perform the normalizations without necessarily having to perform the model validations.
80
-
81
- pre. $ rails console
82
- > product = Product.new name: " hard drive"
83
- => #<Product id: nil, name: " hard drive", price: nil>
84
- > product.apply_normalizations
85
- > product
86
- => #<Product id: nil, name: "hard drive", price: nil>
87
-
88
- h3. Setting normalization rules
89
-
90
- The @:with@ option allows to bind filters to one or more attribute.
91
-
92
- pre. class Product < ActiveRecord::Base
93
- normalize :name, :with => :upcase
94
- end
95
-
96
- pre. $ rails console
97
- > Product.normalize_filters
98
- => { :name => { :upcase => [] } }
99
-
100
- The filters passed throught @:with@ option will not stack with default filters.
101
- When "normalize":http://rubydoc.info/gems/normatron/Normatron/Extensions/ActiveRecord/ClassMethods#normalize-instance_method method is used multiple times for the same attribute, it will stack the filter bindings.
102
-
103
- pre. class Product < ActiveRecord::Base
104
- normalize :name
105
- normalize :name, :with => :upcase
106
- end
107
-
108
- pre. $ rails console
109
- > Product.normalize_filters
110
- => { :name => { :squish => [],
111
- :blank => [],
112
- :upcase => [] } }
113
-
114
- The same result can be obtained using:
115
-
116
- pre. normalize :name, :with => [:squish, :blank, :upcase]
117
-
118
- Some filters may use arguments to perferm normalizations.
119
- There are two approaches to deal with filter arguments in Normatron:
120
-
121
- a) Using a Hash where the key is the filter name and the value is an arguments Array.
122
-
123
- pre. class Product < ActiveRecord::Base
124
- normalize :name, :with => [ { :keep => [:Latin], :remove => [:Nd, :Zs] } ]
125
- normalize :description, :with => :squeeze
126
- normalize :brand, :with => { :squeeze => ["a-z"] }
127
- end
128
-
129
- b) Using an Array where the first element if the attribute name and rest is the filter arguments.
130
-
131
- pre. class Product < ActiveRecord::Base
132
- normalize :name, :with => [ [:keep, :Latin], [:remove, :Nd, :Zs] ]
133
- normalize :description, :with => :squeeze
134
- normalize :brand, :with => [ [:squeeze, "a-z"] ]
135
- end
136
-
137
- Both ways will produce the same result:
138
-
139
- pre. $ rails console
140
- > Product.normalize_filters
141
- => { :name => { :keep => [:Latin],
142
- :remove => [:Nd, :Zs] },
143
- :description => { :squeeze => [] },
144
- :brand => { :squeeze => ["a-z"] } }
145
-
146
- h3. Using instance method as filter
147
-
148
- Create an instance method returning the value as you want.
149
- The first argument is mandatory, and will receive the original value of the attribute.
150
- If you need to use aditional arguments or varargs, just add them after the first argument.
151
-
152
- <pre>
153
- # ./app/models/client.rb
154
- class Client < ActiveRecord::Base
155
- normalize :phone, :with => [:custom_a, [:custom_b, :a, :b], {:custom_c => [:a, :b, :c]}]
156
- normalize :mobile, :with => [:custom_a, {:custom_b => [:a, :b]}, [:custom_c, :a, :b, :c]]
157
-
158
- def custom_a(value)
159
- # ...
160
- end
161
-
162
- def custom_b(value, *args)
163
- # ...
164
- end
165
-
166
- def custom_c(value, arg_a, arg_b, arg_c)
167
- # ...
168
- end
169
- end
170
- </pre>
171
-
172
- h2. Filters
173
-
174
- Information about native filters and how to use them can be found in "Normatron::Filters(Normatron::Filters Rubydoc)":http://rubydoc.info/gems/normatron/Normatron/Filters.
175
-
176
- h1. Contributing
177
-
178
- There are several ways to make this gem even better:
179
-
180
- * Forking this project
181
- * Adding new features or bug fixes
182
- * Making tests
183
- * Commiting your changes
184
- * Reporting any bug or unexpected behavior
185
- * Suggesting any improvement
186
- * Sharing with your friends, forums, communities, job, etc...
187
- * Helping users with difficulty using this gem
188
- * Paying me a beer =]
189
-
190
- h1. Credits
191
-
192
- This gem was initially inspired on:
193
- * "normalize_attributes (normalize_attributes Gem)":https://github.com/fnando/normalize_attributes - I liked the cleaner code and simplicity of this gem.
194
- * "attribute_normalizer (attribute_normalizer Gem)":https://github.com/mdeering/attribute_normalizer - Very powerful.
195
-
196
- The idea is to mix the good things of both gems, adding some features and changing something to fit my taste.
197
-
198
- h1. License
199
-
200
- See file attached to source code or click "here":https://github.com/fernandors87/normatron/blob/master/MIT-LICENSE.