normatron 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/README.textile +35 -48
  2. data/Rakefile +6 -7
  3. data/lib/generators/normatron/install_generator.rb +23 -0
  4. data/lib/generators/normatron/templates/normatron.rb +4 -0
  5. data/lib/normatron.rb +22 -8
  6. data/lib/normatron/configuration.rb +26 -22
  7. data/lib/normatron/extensions.rb +8 -0
  8. data/lib/normatron/extensions/active_record.rb +20 -15
  9. data/lib/normatron/filters.rb +26 -379
  10. data/lib/normatron/filters/blank_filter.rb +29 -0
  11. data/lib/normatron/filters/camelize_filter.rb +50 -0
  12. data/lib/normatron/filters/capitalize_filter.rb +29 -0
  13. data/lib/normatron/filters/chomp_filter.rb +34 -0
  14. data/lib/normatron/filters/dasherize_filter.rb +25 -0
  15. data/lib/normatron/filters/downcase_filter.rb +29 -0
  16. data/lib/normatron/filters/dump_filter.rb +27 -0
  17. data/lib/normatron/filters/helpers.rb +44 -0
  18. data/lib/normatron/filters/keep_filter.rb +100 -0
  19. data/lib/normatron/filters/remove_filter.rb +37 -0
  20. data/lib/normatron/filters/squeeze_filter.rb +30 -0
  21. data/lib/normatron/filters/squish_filter.rb +28 -0
  22. data/lib/normatron/filters/strip_filter.rb +33 -0
  23. data/lib/normatron/filters/swapcase_filter.rb +30 -0
  24. data/lib/normatron/filters/titleize_filter.rb +29 -0
  25. data/lib/normatron/filters/underscore_filter.rb +45 -0
  26. data/lib/normatron/filters/upcase_filter.rb +29 -0
  27. data/lib/normatron/version.rb +3 -0
  28. data/spec/normatron/configuration_spec.rb +60 -0
  29. data/spec/normatron/extensions/active_record_spec.rb +96 -0
  30. data/spec/normatron/filters/blank_filter_spec.rb +15 -0
  31. data/spec/normatron/filters/camelize_filter_spec.rb +42 -0
  32. data/spec/normatron/filters/capitalize_filter_spec.rb +14 -0
  33. data/spec/normatron/filters/chomp_filter_spec.rb +15 -0
  34. data/spec/normatron/filters/dasherize_filter_spec.rb +9 -0
  35. data/spec/normatron/filters/downcase_filter_spec.rb +10 -0
  36. data/spec/normatron/filters/dump_filter_spec.rb +10 -0
  37. data/spec/normatron/filters/keep_filter_spec.rb +86 -0
  38. data/spec/normatron/filters/remove_filter_spec.rb +86 -0
  39. data/spec/normatron/filters/squeeze_filter_spec.rb +10 -0
  40. data/spec/normatron/filters/squish_filter_spec.rb +12 -0
  41. data/spec/normatron/filters/strip_filter_spec.rb +12 -0
  42. data/spec/normatron/filters/swapcase_filter_spec.rb +12 -0
  43. data/spec/normatron/filters/titleize_filter_spec.rb +12 -0
  44. data/spec/normatron/filters/underscore_filter_spec.rb +26 -0
  45. data/spec/normatron/filters/upcase_filter_spec.rb +10 -0
  46. data/spec/normatron_spec.rb +28 -2
  47. data/spec/spec_helper.rb +37 -4
  48. data/spec/support/my_filters.rb +7 -0
  49. data/spec/support/user_model.rb +14 -0
  50. metadata +64 -13
  51. data/spec/configuration_spec.rb +0 -53
  52. data/spec/extensions/active_record_spec.rb +0 -114
  53. data/spec/filters_spec.rb +0 -442
  54. data/spec/support/model_model.rb +0 -3
  55. data/spec/support/schema.rb +0 -7
@@ -1,6 +1,6 @@
1
1
  h1. Normatron
2
2
 
3
- Normatron is an Ruby On Rails plugin that perform attribute normalizations for ActiveRecord objects.
3
+ Normatron is a Ruby On Rails plugin that perform attribute normalizations for ActiveRecord objects.
4
4
  With it you can normalize attributes to the desired format before saving them in the database.
5
5
  This gem inhibits the work of having to override attributes or create a specific method to perform most of the normalizations.
6
6
 
@@ -18,6 +18,10 @@ Or install it by yourself:
18
18
 
19
19
  pre. $ gem install normatron
20
20
 
21
+ Then run the generator:
22
+
23
+ pre. $ rails generator normatron:install
24
+
21
25
  h2. The problem
22
26
 
23
27
  Suppose you have a product model as the following:
@@ -41,15 +45,15 @@ And we want the _name_ attribute be uppercased before saving it into the databas
41
45
  The most usual approach to do this includes:
42
46
 
43
47
  * Override the _name_ setter and convert the value to an uppercased string.
44
- * Use the "_before_validation_":http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html callback to run a method or block doing the task.
48
+ * Write a method or block and bind it to the "before_validation":http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html callback.
45
49
 
46
50
  Both ways are ilenegant, boring, error prone and very expensive.
47
51
  What led me to make this gem and offer a third way to solve this issue:
48
52
 
49
53
  h2. Usage
50
54
 
51
- Normatron uses "_:squish_":http://rubydoc.info/gems/normatron/Normatron/Filters#squish-class_method and "_:blank_":http://rubydoc.info/gems/normatron/Normatron/Filters#blank-class_method as default filters.
52
- These filters are applied to all attributes in @normalize@ function, since no options is given.
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.
53
57
 
54
58
  pre. # ./app/models/products.rb
55
59
  class Product < ActiveRecord::Base
@@ -58,48 +62,30 @@ class Product < ActiveRecord::Base
58
62
  end
59
63
 
60
64
  pre. $ rails console
61
- > p1 = Product.create name: " memory card "
65
+ > memory = Product.create name: " memory card "
62
66
  => #<Product id: nil, name: "memory card", price: nil>
63
- > p2 = Product.create name: " "
67
+ > null = Product.create name: " "
64
68
  => #<Product id: nil, name: nil, price: nil>
65
69
 
66
- h3. The _normalize_attributes_ method
70
+ h3. Configurations
67
71
 
68
- 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 _normalize_attributes_ method, thought "before_validation":http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html callback.
69
- 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.
72
+ "normatron:install":http://rubydoc.info/gems/normatron/Normatron/InstallGenerator generator creates a configuration file located at @./config/initializers/normatron.rb@.
70
73
 
71
- pre. $ rails console
72
- > p = Product.new name: " hard drive"
73
- => #<Product id: nil, name: " hard drive", price: nil>
74
- > p.normalize_attributes
75
- > p
76
- => #<Product id: nil, name: "hard drive", price: nil>
74
+ For now, you can set the default normalization filters or disable normatron by commenting the @add_orm@ line.
77
75
 
78
- h3. The _normalize_options_ method
76
+ h3. Applying normalizations
79
77
 
80
- To read the normalization filters set for a model, just call the @normalize_options@ method.
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.
81
80
 
82
81
  pre. $ rails console
83
- > Product.normalize_options
84
- => { name: { :squish => [], :blank => [] } }
85
-
86
- This method returns a Hash, where the keys are the attribute names and values are another Hash with filter options.
87
- In the filter options Hash, keys represent the filter names and the values are Arrays containing arguments to be passed to filter methods.
88
- The following example is a data structure returned by the @normalize_options@ method:
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>
89
87
 
90
- pre. $ rails console
91
- > MyModel.normalize_options
92
- => { :attribute_a => { :filter_a => [] },
93
- :attribute_b => { :filter_a => [],
94
- :filter_b => [arg_a, arg_b] },
95
- :attribute_c => { :filter_b => [arg_a, arg_b],
96
- :filter_c => [arg],
97
- :filter_d => [] },
98
- :attribute_d => { :filter_a => [],
99
- :filter_c => [arg],
100
- :filter_d => [arg_a, arg_b, arg_c, arg_d] } }
101
-
102
- h3. The _:with_ option
88
+ h3. Setting normalization rules
103
89
 
104
90
  The @:with@ option allows to bind filters to one or more attribute.
105
91
 
@@ -108,11 +94,11 @@ pre. class Product < ActiveRecord::Base
108
94
  end
109
95
 
110
96
  pre. $ rails console
111
- > Product.normalize_options
97
+ > Product.normalize_filters
112
98
  => { :name => { :upcase => [] } }
113
99
 
114
100
  The filters passed throught @:with@ option will not stack with default filters.
115
- When @normalize@ method is used multiple times for the same attribute, it will stack the filter bindings.
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.
116
102
 
117
103
  pre. class Product < ActiveRecord::Base
118
104
  normalize :name
@@ -120,7 +106,7 @@ pre. class Product < ActiveRecord::Base
120
106
  end
121
107
 
122
108
  pre. $ rails console
123
- > Product.normalize_options
109
+ > Product.normalize_filters
124
110
  => { :name => { :squish => [],
125
111
  :blank => [],
126
112
  :upcase => [] } }
@@ -137,7 +123,7 @@ a) Using a Hash where the key is the filter name and the value is an arguments A
137
123
  pre. class Product < ActiveRecord::Base
138
124
  normalize :name, :with => [ { :keep => [:Latin], :remove => [:Nd, :Zs] } ]
139
125
  normalize :description, :with => :squeeze
140
- normalize :brand, :with => [ { :squeeze => ["a-z"] }, { :keep => [:Word] } ]
126
+ normalize :brand, :with => { :squeeze => ["a-z"] }
141
127
  end
142
128
 
143
129
  b) Using an Array where the first element if the attribute name and rest is the filter arguments.
@@ -145,18 +131,17 @@ b) Using an Array where the first element if the attribute name and rest is the
145
131
  pre. class Product < ActiveRecord::Base
146
132
  normalize :name, :with => [ [:keep, :Latin], [:remove, :Nd, :Zs] ]
147
133
  normalize :description, :with => :squeeze
148
- normalize :brand, :with => [ [:squeeze, "a-z"], [:keep, :Word] ]
134
+ normalize :brand, :with => [ [:squeeze, "a-z"] ]
149
135
  end
150
136
 
151
137
  Both ways will produce the same result:
152
138
 
153
139
  pre. $ rails console
154
- > Product.normalize_options
140
+ > Product.normalize_filters
155
141
  => { :name => { :keep => [:Latin],
156
142
  :remove => [:Nd, :Zs] },
157
143
  :description => { :squeeze => [] },
158
- :brand => { :squeeze => ["a-z"],
159
- :keep => [:Word] } }
144
+ :brand => { :squeeze => ["a-z"] } }
160
145
 
161
146
  h3. Using instance method as filter
162
147
 
@@ -186,7 +171,7 @@ end
186
171
 
187
172
  h2. Filters
188
173
 
189
- 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. All methods have a short description of what they do and some examples of how to use them.
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.
190
175
 
191
176
  h1. Contributing
192
177
 
@@ -204,9 +189,11 @@ There are several ways to make this gem even better:
204
189
 
205
190
  h1. Credits
206
191
 
207
- This gem was initially inspired on these gems:
208
- * "normalize_attributes (normalize_attributes Gem)":https://github.com/fnando/normalize_attributes - "fnando (fnando's Github)":https://github.com/fnando
209
- * "attribute_normalizer (attribute_normalizer Gem)":https://github.com/mdeering/attribute_normalizer - "mdeering (mdeering's Github)":https://github.com/mdeering
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.
210
197
 
211
198
  h1. License
212
199
 
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env rake
2
+ =begin
2
3
  begin
3
4
  require 'bundler/setup'
4
5
  rescue LoadError
@@ -22,13 +23,11 @@ end
22
23
 
23
24
  Bundler::GemHelper.install_tasks
24
25
 
25
- require 'rake/testtask'
26
+ =end
27
+ require 'rspec/core/rake_task'
26
28
 
27
- Rake::TestTask.new(:test) do |t|
28
- t.libs << 'lib'
29
- t.libs << 'test'
30
- t.pattern = 'test/**/*_test.rb'
31
- t.verbose = false
29
+ RSpec::Core::RakeTask.new(:spec) do |task|
30
+ task.rspec_opts = '--color -f d'
32
31
  end
33
32
 
34
- task :default => :test
33
+ task :default => :spec
@@ -0,0 +1,23 @@
1
+ module Normatron
2
+ ##
3
+ # Creates all necessary files to run Normatron in your Ruby On Rails project.
4
+ #
5
+ # These files are:
6
+ # * config/initializers/normatron.rb
7
+ #
8
+ # *Usage:*
9
+ # $ rails generator normatron:install
10
+ class InstallGenerator < Rails::Generators::Base
11
+ source_root File.expand_path('../templates', __FILE__)
12
+ desc "Create all necessary files to run Normatron in your Ruby On Rails project."
13
+
14
+ ##
15
+ # Copy files from templates to their respective destination
16
+ #
17
+ # These files are:
18
+ # * config/initializers/normatron.rb
19
+ def copy_files
20
+ copy_file "normatron.rb", "config/initializers/normatron.rb"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ Normatron.setup do |config|
2
+ config.default_filters = :blank, :squish
3
+ config.add_orm Normatron::Extensions::ActiveRecord
4
+ end
@@ -1,20 +1,34 @@
1
1
  require "normatron/configuration"
2
- require "normatron/extensions/active_record"
2
+ require "normatron/extensions"
3
3
 
4
4
  module Normatron
5
- VERSION = "0.2.1"
6
-
7
5
  class << self
8
6
  def configuration
9
7
  @configuration ||= Configuration.new
10
8
  end
11
9
  alias :config :configuration
12
10
 
13
- def configure
11
+ def setup
14
12
  yield(configuration)
15
13
  end
16
- alias :setup :configure
17
- end
18
- end
19
14
 
20
- ActiveRecord::Base.send(:include, Normatron::Extensions::ActiveRecord) if defined?(ActiveRecord::Base)
15
+ def build_hash(*terms)
16
+ terms.flatten!(1)
17
+
18
+ filters_hash = {}
19
+ terms.each do |term|
20
+ case term
21
+ when Array
22
+ key, value = term.first, term.drop(1)
23
+ filters_hash[key.to_sym] = value.kind_of?(Array) && value.empty? ? nil : value
24
+ when Hash
25
+ term.each { |key, value| filters_hash[key.to_sym] = *value }
26
+ else
27
+ filters_hash[term.to_sym] = nil
28
+ end
29
+ end
30
+
31
+ filters_hash
32
+ end
33
+ end
34
+ end
@@ -1,34 +1,38 @@
1
+ require 'normatron/filters'
2
+
1
3
  module Normatron
2
4
  class Configuration
3
- def self.clean_filters(*with)
4
- elements = with.flatten(1)
5
- raise "empty" unless elements.any?
6
-
7
- result = {}
8
- elements.each do |e|
9
- case e
10
- when Array
11
- result[e[0].to_sym] = e[1..-1]
12
- when Hash
13
- e.each { |key, value| result[key] = *value.flatten(1) }
14
- else
15
- result[e.to_sym] = []
16
- end
17
- end
18
-
19
- result
20
- end
5
+ attr_reader :default_filters
6
+ attr_accessor :filters
21
7
 
22
8
  def initialize
23
- @default_filters = { squish: [], blank: [] }
9
+ @default_filters = { blank: nil, squish: nil }
10
+
11
+ @filters = {}
12
+ @filters[:blank] = Normatron::Filters::BlankFilter
13
+ @filters[:camelize] = Normatron::Filters::CamelizeFilter
14
+ @filters[:capitalize] = Normatron::Filters::CapitalizeFilter
15
+ @filters[:chomp] = Normatron::Filters::ChompFilter
16
+ @filters[:dasherize] = Normatron::Filters::DasherizeFilter
17
+ @filters[:downcase] = Normatron::Filters::DowncaseFilter
18
+ @filters[:dump] = Normatron::Filters::DumpFilter
19
+ @filters[:keep] = Normatron::Filters::KeepFilter
20
+ @filters[:remove] = Normatron::Filters::RemoveFilter
21
+ @filters[:squeeze] = Normatron::Filters::SqueezeFilter
22
+ @filters[:squish] = Normatron::Filters::SquishFilter
23
+ @filters[:strip] = Normatron::Filters::StripFilter
24
+ @filters[:swapcase] = Normatron::Filters::SwapcaseFilter
25
+ @filters[:titleize] = Normatron::Filters::TitleizeFilter
26
+ @filters[:underscore] = Normatron::Filters::UnderscoreFilter
27
+ @filters[:upcase] = Normatron::Filters::UpcaseFilter
24
28
  end
25
29
 
26
- def default_filters
27
- @default_filters
30
+ def add_orm(extension)
31
+ extension::ORM_CLASS.send(:include, extension)
28
32
  end
29
33
 
30
34
  def default_filters=(filters)
31
- @default_filters = self.class.clean_filters(filters)
35
+ @default_filters = Normatron.build_hash(filters)
32
36
  end
33
37
  end
34
38
  end
@@ -0,0 +1,8 @@
1
+ require 'normatron/extensions/active_record'
2
+
3
+ module Normatron
4
+ module Extensions
5
+ class UnknownAttributeError < RuntimeError ; end
6
+ class UnknownFilterError < RuntimeError ; end
7
+ end
8
+ end
@@ -1,16 +1,19 @@
1
- require "normatron/filters"
1
+ require 'active_record'
2
2
 
3
3
  module Normatron
4
4
  module Extensions
5
5
  module ActiveRecord
6
+
7
+ ORM_CLASS = ::ActiveRecord::Base
8
+
6
9
  def self.included(base)
7
10
  base.instance_eval do
8
11
  extend ClassMethods
9
12
  include InstanceMethods
10
- before_validation :normalize_attributes
13
+ before_validation :apply_normalizations
11
14
 
12
15
  class << self
13
- attr_accessor :normalize_options
16
+ attr_accessor :normalize_filters
14
17
  end
15
18
  end
16
19
  end
@@ -19,38 +22,40 @@ module Normatron
19
22
  def normalize(*args)
20
23
  # Check existence of all attributes
21
24
  options = args.extract_options!
22
- args, columns = args.map(&:to_sym), column_names.map(&:to_sym)
23
- raise "attribute" if (columns & args).size != args.size
25
+ filters, columns = args.map(&:to_s), column_names
26
+ raise UnknownAttributeError if (columns & filters).size != filters.size
24
27
 
25
28
  # Specify the use of default filters or not
26
29
  if options[:with].nil? || options[:with].blank?
27
30
  new_filters = Normatron.config.default_filters
28
31
  else
29
- new_filters = Normatron::Configuration.clean_filters(options[:with])
32
+ new_filters = Normatron.build_hash(options[:with])
30
33
  end
31
34
 
32
35
  # Append to older filters hash
33
- @normalize_options ||= {}
34
- @normalize_options =
35
- args.reduce(@normalize_options) do |hash, att|
36
- filters = (@normalize_options[att] || {}).merge(new_filters)
36
+ @normalize_filters ||= {}
37
+ @normalize_filters =
38
+ args.reduce(@normalize_filters) do |hash, att|
39
+ filters = (@normalize_filters[att] || {}).merge(new_filters)
37
40
  hash.merge({att => filters})
38
41
  end
39
42
  end
40
43
  end
41
44
 
42
45
  module InstanceMethods
43
- def normalize_attributes
44
- self.class.normalize_options.each do |attribute, filters|
46
+ def apply_normalizations
47
+ named_filters = Normatron.configuration.filters
48
+
49
+ self.class.normalize_filters.each do |attribute, filters|
45
50
  value = send("#{attribute}_before_type_cast") || send(attribute)
46
51
 
47
52
  filters.each do |filter, args|
48
53
  if self.respond_to? filter
49
54
  value = send(filter, value, *args)
50
- elsif Normatron::Filters.respond_to? filter
51
- value = Normatron::Filters.send(filter, value, *args)
55
+ elsif !named_filters[filter].nil?
56
+ value = named_filters[filter].evaluate(value, *args)
52
57
  else
53
- raise "Filter '#{filter}' wasn't found."
58
+ raise UnknownFilterError
54
59
  end
55
60
  end
56
61
 
@@ -1,384 +1,31 @@
1
- # encoding: UTF-8
2
-
3
- require 'active_support/multibyte/chars'
4
- require 'active_support/core_ext/string'
5
- require 'active_support/inflector/inflections'
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'
6
17
 
7
18
  module Normatron
19
+ # Top-Level namespace of all native Normatron filters.
20
+ #
21
+ # All filters share some characteristics:
22
+ # * They have the <code>Filter</code> suffix in the name.
23
+ # * Has a class method called <code>evaluate</code>, which runs what the filter claims to do.
24
+ # * 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.
8
27
  module Filters
9
- extend self
10
-
11
- ##
12
- # Returns a <tt>Nil</tt> object for a blank string or the string itself otherwise.
13
- #
14
- # @example
15
- # blank("") #=> nil
16
- # blank(" ") #=> nil
17
- # blank(" \n ") #=> nil
18
- # blank("1") #=> "1"
19
- # blank("It's blank?") #=> "It's blank?"
20
- # blank(123) #=> 123
21
- #
22
- # # ActiveRecord normalizer usage
23
- # normalize :attribute_a, :with => :blank
24
- # normalize :attribute_b, :with => [:custom_filter, :blank]
25
- # @param [String] value A character sequence
26
- # @return [String, Nil] The object itself or nil
27
- # @see http://api.rubyonrails.org/classes/String.html#method-i-blank-3F String#blank?
28
- def blank(value)
29
- return value unless string?(value) && value.to_s.blank?
30
- nil
31
- end
32
-
33
- ##
34
- # Converts strings to UpperCamelCase.
35
- # If the argument to camelize is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
36
- # camelize will also convert <tt>'/'</tt> to <tt>'::'</tt> which is useful for converting paths to namespaces.
37
- #
38
- # @example
39
- # camelize("active_record/errors") #=> "ActiveRecord::Errors"
40
- # camelize("active_record/errors", :upper) #=> "ActiveRecord::Errors"
41
- # camelize("active_record/errors", :lower) #=> "activeRecord::Errors"
42
- # camelize(123) #=> 123
43
- #
44
- # # ActiveRecord normalizer usage
45
- # normalize :attribute_a, :with => :camelize
46
- # normalize :attribute_b, :with => [:custom_filter, :camelize]
47
- # normalize :attribute_c, :with => [[:camelize, :lower]]
48
- # normalize :attribute_d, :with => [{:camelize => :lower}]
49
- # normalize :attribute_e, :with => [:custom_filter, [:camelize, :lower]]
50
- # normalize :attribute_f, :with => [:custom_filter, {:camelize => :lower}]
51
- # @param [String] value A character sequence
52
- # @param [Symbol] first_letter_case <tt>:lower</tt> for lowerCamelCase or <tt>:upper</tt> for UpperCamelCase
53
- # @return [String, Object] The camelized String or the object itself
54
- def camelize(value, first_letter_case = :upper)
55
- case value
56
- when String
57
- string = value
58
- when ActiveSupport::Multibyte::Chars
59
- string = value.to_s
60
- else
61
- return value
62
- end
63
-
64
- inflections = ActiveSupport::Inflector::Inflections.instance
65
- if first_letter_case == :upper
66
- string = string.sub(/^[\p{L}\d]*/u) { inflections.acronyms[$&] || capitalize($&) }
67
- elsif first_letter_case == :lower
68
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[\p{L}_])|\p{Word}*_)/u) { downcase($&) }
69
- else
70
- raise "Use options :upper or :lower for Normatron::Filters#camelize"
71
- end
72
- string.gsub!(/(?:_|(\/))([\p{L}\d]*)/iu) { "#{$1}#{inflections.acronyms[$2] || capitalize($2)}" }.gsub!('/', '::')
73
-
74
- (value.is_a?(String) && string) || string.mb_chars
75
- end
76
-
77
- ##
78
- # Makes the first character uppercase and lowercase others.
79
- #
80
- # @example
81
- # capitalize("jESSE PINK") #=> "Jesse pink"
82
- # capitalize("wALTER WHITE") #=> "Walter white"
83
- # capitalize(" mr. Fring") #=> " mr. fring"
84
- # capitalize(123) #=> 123
85
- #
86
- # # ActiveRecord normalizer usage
87
- # normalize :attribute_a, :with => :capitalize
88
- # normalize :attribute_b, :with => [:custom_filter, :capitalize]
89
- # @param [String] value A character sequence
90
- # @return [String, Object] The capitalized String or the object itself
91
- # @see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-capitalize String#capitalize
92
- # @see http://api.rubyonrails.org/classes/ActiveSupport/Multibyte/Chars.html#method-i-capitalize ActiveSupport::Multibyte::Chars#capitalize
93
- def capitalize(value)
94
- (string?(value) && eval_send(:capitalize, value)) || value
95
- end
96
-
97
- ##
98
- # Remove the given record separator from the end of the string (If present).
99
- # If <tt>$/</tt> has not been changed from the default Ruby record separator,
100
- # then chomp also removes carriage return characters (that is it will remove <tt>\n</tt>, <tt>\r</tt>, and <tt>\r\n</tt>).
101
- #
102
- # @example
103
- # chomp("Bon \n Scott\n") #=> "Bon \n Scott"
104
- # chomp("Bon \n Scott\r") #=> "Bon \n Scott"
105
- # chomp("Bon \n Scott\r\n") #=> "Bon \n Scott"
106
- # chomp("Bon \n Scott\n\r") #=> "Bon \n Scott\n"
107
- # chomp("Bon \n Scott", "t") #=> "Bon \n Scot"
108
- # chomp("Bon \n Scott", "Scott") #=> "Bon \n "
109
- # chomp("Bon \n Scott", " \n Scott") #=> "Bon"
110
- # chomp(100) #=> 100
111
- #
112
- # # ActiveRecord normalizer usage
113
- # normalize :attribute_a, :with => :chomp
114
- # normalize :attribute_b, :with => [:custom_filter, :chomp]
115
- # normalize :attribute_c, :with => [[:chomp, "x"]]
116
- # normalize :attribute_d, :with => [{:chomp => "y"}]
117
- # normalize :attribute_e, :with => [:custom_filter, [:chomp, "z"]]
118
- # normalize :attribute_f, :with => [:custom_filter, {:chomp => "\t"}]
119
- # @param [String] value A character sequence
120
- # @param [String] separator A character sequence
121
- # @return [String, Object] The chopped String or the object itself
122
- # @see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-chomp String#chomp
123
- def chomp(value, separator=$/)
124
- (string?(value) && eval_send(:chomp, value, separator)) || value
125
- end
126
-
127
- ##
128
- # Replaces all underscores with dashes.
129
- #
130
- # @example
131
- # dasherize("monty_python") #=> "monty-python"
132
- # dasherize("_.·-'*'-·._") #=> "-.·-'*'-·.-"
133
- # dasherize(123) #=> 123
134
- #
135
- # # ActiveRecord normalizer usage
136
- # normalize :attribute_a, :with => :dasherize
137
- # normalize :attribute_b, :with => [:custom_filter, :dasherize]
138
- # @param [String] value A character sequence
139
- # @return [String, Object] The dasherized String or the object itself
140
- # @see http://api.rubyonrails.org/classes/String.html#method-i-dasherize String#dasherize
141
- def dasherize(value)
142
- (string?(value) && eval_send(:dasherize, value)) || value
143
- end
144
-
145
- ##
146
- # Lowercase all characters.
147
- #
148
- # @example
149
- # downcase("VEGETA!!!") #=> "vegeta!!!"
150
- # downcase(123) #=> 123
151
- #
152
- # # ActiveRecord normalizer usage
153
- # normalize :attribute_a, :with => :downcase
154
- # normalize :attribute_b, :with => [:custom_filter, :downcase]
155
- # @param [String] value A character sequence
156
- # @return [String, Object] The lowercased String or the object itself
157
- # @see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-downcase String#downcase
158
- # @see http://api.rubyonrails.org/classes/ActiveSupport/Multibyte/Chars.html#method-i-downcase ActiveSupport::Multibyte::Chars#downcase
159
- def downcase(value)
160
- (string?(value) && eval_send(:downcase, value)) || value
161
- end
162
-
163
- ##
164
- # Creates a literal string representation with all nonprinting characters
165
- # replaced by <tt>\\n</tt> notation and all special characters escaped.
166
- #
167
- # @example
168
- # dump("I'm not\na \"clubber\"...") #=> "\"I'm not\\na \\\"clubber\\\"...\""
169
- # dump("I'm not\na \"clubber\"...") #== '"I\'m not\na \"clubber\"..."'
170
- # dump('I\'m not\na "clubber"...') #=> "\"I'm not\\\\na \\\"clubber\\\"...\""
171
- # dump('I\'m not\na "clubber"...') #== '"I\'m not\\\na \"clubber\"..."'
172
- # dump(100) #=> 100
173
- #
174
- # # ActiveRecord normalizer usage
175
- # normalize :attribute_a, :with => :dump
176
- # normalize :attribute_b, :with => [:custom_filter, :dump]
177
- # @param [String] value A character sequence
178
- # @return [String, Object] The dumpped String or the object itself
179
- # @see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-dump String#dump
180
- def dump(value)
181
- (string?(value) && eval_send(:dump, value)) || value
182
- end
183
-
184
- ##
185
- # Keep only the specified characters.
186
- # Details about the options can be found in the Regexp class documentation.
187
- #
188
- # @example
189
- # keep("Doom 3", :L) #=> "Doom" equivalent to /\p{L}/u
190
- # keep("Doom 3", :N) #=> "3" equivalent to /\p{N}/u
191
- # keep("Doom 3", :L, :N) #=> "Doom3" equivalent to /\p{L}\p{N}/u
192
- # keep("Doom 3", :Lu, :N) #=> "D3" equivalent to /\p{Lu}\p{N}/u
193
- # keep("Doom ˩", :Latin) #=> "Doom" equivalent to /\p{Latin}/u
194
- #
195
- # # For normalizations
196
- # normalize :attribute_a, :with => [[:keep, :Lu, :N]]
197
- # normalize :attribute_b, :with => [:custom_filter, [:keep, :L, :Nd]]
198
- # normalize :attribute_c, :with => [:custom_filter, {:keep => [:Latin, :Z]}]
199
- # @param [String] value A character sequence
200
- # @param [[Symbol]*] args Array of Symbols equivalent to Regexp for \\p{} construct.
201
- # @return [String, Object] The clean character sequence or the object itself
202
- # @see http://www.ruby-doc.org/core-1.9.3/Regexp.html Regexp
203
- def keep(value, *args)
204
- eval_regexp(value, true, args)
205
- end
206
-
207
- ##
208
- # Remove trailing spaces.
209
- #
210
- # @example
211
- # lstrip(" copyleft ") #=> "copyleft "
212
- #
213
- # # For normalizations
214
- # normalize :attribute, :with => [:custom_filter, :lstrip]
215
- # @param [String] value A character sequence
216
- # @return [String, Object] The character sequence without trailing spaces or the object itself
217
- # @see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-lstrip String#lstrip
218
- def lstrip(value)
219
- (string?(value) && eval_strip(value, true, false)) || value
220
- end
221
-
222
- ##
223
- # Remove only the specified characters.
224
- # Details about the options can be found in the Regexp class documentation.
225
- #
226
- # @example
227
- # remove("Quake 3", :L) #=> "3" equivalent to /\p{L}/u
228
- # remove("Quake 3", :N) #=> "Quake " equivalent to /\p{N}/u
229
- # remove("Quake 3", :L, :N) #=> " " equivalent to /\p{L}\p{N}/u
230
- # remove("Quake 3", :Lu, :N) #=> "uake " equivalent to /\p{Lu}\p{N}/u
231
- # remove("Quake ˩", :Latin) #=> "Quake" equivalent to /\p{Latin}/u
232
- #
233
- # # For normalizations
234
- # normalize :attribute_a, :with => [[:remove, :Lu, :N]]
235
- # normalize :attribute_b, :with => [:custom_filter, [:remove, :L, :Nd]]
236
- # normalize :attribute_c, :with => [:custom_filter, {:remove => [:Latin, :Z]}]
237
- # @param [String] value A character sequence
238
- # @param [[Symbol]*] args Array of Symbols equivalent to Regexp for \\p{} construct.
239
- # @return [String, Object] The clean character sequence or the object itself
240
- # @see http://www.ruby-doc.org/core-1.9.3/Regexp.html Regexp
241
- def remove(value, *args)
242
- eval_regexp(value, false, args)
243
- end
244
-
245
- ##
246
- # Remove leading spaces.
247
- #
248
- # @example
249
- # rstrip(" copyright ") #=> " copyright"
250
- #
251
- # # For normalizations
252
- # normalize :attribute, :with => [:custom_filter, :rstrip]
253
- # @param [String] value A character sequence
254
- # @return [String, Object] The character sequence without leading spaces or the object itself
255
- # @see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-rstrip String#rstrip
256
- def rstrip(value)
257
- (string?(value) && eval_strip(value, false, true)) || value
258
- end
259
-
260
- ##
261
- # Remove multiple occurences of the same character.
262
- # If no option are given, all runs of identical characters are replaced by a single character.
263
- #
264
- # @example
265
- # squeeze("yellow moon") #=> "yelow mon"
266
- # squeeze(" now is the", " ") #=> " now is the"
267
- # squeeze("putters shoot balls", "m-z") #=> "puters shot balls"
268
- #
269
- # # For normalizations
270
- # normalize :attribute_a, :with => [:custom_filter, :squeeze]
271
- # normalize :attribute_b, :with => [:custom_filter, [:squeeze, "a-f"]]
272
- # normalize :attribute_c, :with => [:custom_filter, {:squeeze => ["a-f"]}]
273
- # @param [String] value A character sequence
274
- # @param [[String]*] args Chars to be affected
275
- # @return [String, Object] The clean character sequence or the object itself
276
- # @see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-squeeze String#squeeze
277
- def squeeze(value, *args)
278
- return value unless string?(value)
279
- (args.any? && value.squeeze(args.first)) || value.squeeze
280
- end
281
-
282
- ##
283
- # Strip and remove multiple spaces.
284
- #
285
- # @example
286
- # squish(" the \n simpsons ") #=> "the simpsons"
287
- #
288
- # # For normalizations
289
- # normalize :attribute, :with => [:custom_filter, :squish]
290
- # @param [String] value A character sequence
291
- # @return [String, Object] The clean character sequence or the object itself
292
- #
293
- # @see http://api.rubyonrails.org/classes/String.html#method-i-squish String#squish
294
- def squish(value)
295
- (string?(value) && value.squish) || value
296
- end
297
-
298
- ##
299
- # Remove traling and leading spaces from the string.
300
- #
301
- # @example
302
- # strip(" copy ") #=> "copy"
303
- #
304
- # # For normalizations
305
- # normalize :attribute, :with => [:custom_filter, :strip]
306
- # @param [String] value A character sequence
307
- # @return [String, Object] The stripped character sequence or the object itself
308
- def strip(value)
309
- (string?(value) && eval_strip(value, true, true)) || value
310
- end
311
-
312
- ##
313
- # Replaces uppercased characters by lowercased and vice versa.
314
- #
315
- # @example
316
- # swapcase("AiNn Kmo éH BOM ser v1d4 l0k4") #=> "aInN kMO Éh bom SER V1D4 L0K4"
317
- # swapcase(100) #=> 100
318
- #
319
- # # ActiveRecord normalizer usage
320
- # normalize :attribute_a, :with => :swapcase
321
- # normalize :attribute_b, :with => [:custom_filter, :swapcase]
322
- # @param [String] value A character sequence
323
- # @return [String, Object] The String with case swapped or the object itself
324
- def swapcase(value)
325
- return value unless string?(value)
326
- value.gsub(/./) { |c| (/\p{Lu}/.match(c) && downcase(c) || (/\p{Ll}/.match(c) && upcase(c))) || c }
327
- end
328
-
329
- ##
330
- # Converts all characters to uppercase.
331
- #
332
- # @example
333
- # downcase("kakarotto!!!") #=> "KAKAROTTO!!!"
334
- # downcase(123) #=> 123
335
- #
336
- # # For normalizations
337
- # normalize :attribute, :with => [:custom_filter, :upcase]
338
- # @param [String] value A character sequence
339
- # @return [String, Object] The lowercased String or the object itself
340
- # @see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-upcase String#upcase
341
- # @see http://api.rubyonrails.org/classes/ActiveSupport/Multibyte/Chars.html#method-i-upcase ActiveSupport::Multibyte::Chars#upcase
342
- def upcase(value)
343
- (string?(value) && eval_send(:upcase, value)) || value
344
- end
345
-
346
- protected
347
-
348
- def eval_regexp(value, keep, *args)
349
- return value unless string?(value)
350
-
351
- options = args.flatten.compact.uniq
352
-
353
- regex = options.map{ |s| "\\p{#{s.to_s}}" } * ""
354
- regex.prepend('^') if keep
355
- regex = eval "/[#{regex}]/u"
356
-
357
- value.gsub(regex, "")
358
- end
359
-
360
- def eval_send(method, value, option=nil)
361
- type = value.class
362
- value = value.mb_chars if type == String
363
- if option
364
- value = value.send(method, option)
365
- else
366
- value = value.send(method)
367
- end
368
- (type == String && value.to_s) || value
369
- end
370
-
371
- def eval_strip(value, start, ending)
372
- regex = []
373
- regex << '\A\p{Zs}*' if start
374
- regex << '\p{Zs}*\z' if ending
375
- regex = eval "/#{regex * '|'}/u"
28
+ end
29
+ end
376
30
 
377
- value.gsub(regex, '')
378
- end
379
31
 
380
- def string?(value)
381
- value.is_a?(ActiveSupport::Multibyte::Chars) || value.is_a?(String)
382
- end
383
- end
384
- end