normatron 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.