normatron 0.2.1 → 0.3.0
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.
- data/README.textile +35 -48
- data/Rakefile +6 -7
- data/lib/generators/normatron/install_generator.rb +23 -0
- data/lib/generators/normatron/templates/normatron.rb +4 -0
- data/lib/normatron.rb +22 -8
- data/lib/normatron/configuration.rb +26 -22
- data/lib/normatron/extensions.rb +8 -0
- data/lib/normatron/extensions/active_record.rb +20 -15
- data/lib/normatron/filters.rb +26 -379
- data/lib/normatron/filters/blank_filter.rb +29 -0
- data/lib/normatron/filters/camelize_filter.rb +50 -0
- data/lib/normatron/filters/capitalize_filter.rb +29 -0
- data/lib/normatron/filters/chomp_filter.rb +34 -0
- data/lib/normatron/filters/dasherize_filter.rb +25 -0
- data/lib/normatron/filters/downcase_filter.rb +29 -0
- data/lib/normatron/filters/dump_filter.rb +27 -0
- data/lib/normatron/filters/helpers.rb +44 -0
- data/lib/normatron/filters/keep_filter.rb +100 -0
- data/lib/normatron/filters/remove_filter.rb +37 -0
- data/lib/normatron/filters/squeeze_filter.rb +30 -0
- data/lib/normatron/filters/squish_filter.rb +28 -0
- data/lib/normatron/filters/strip_filter.rb +33 -0
- data/lib/normatron/filters/swapcase_filter.rb +30 -0
- data/lib/normatron/filters/titleize_filter.rb +29 -0
- data/lib/normatron/filters/underscore_filter.rb +45 -0
- data/lib/normatron/filters/upcase_filter.rb +29 -0
- data/lib/normatron/version.rb +3 -0
- data/spec/normatron/configuration_spec.rb +60 -0
- data/spec/normatron/extensions/active_record_spec.rb +96 -0
- data/spec/normatron/filters/blank_filter_spec.rb +15 -0
- data/spec/normatron/filters/camelize_filter_spec.rb +42 -0
- data/spec/normatron/filters/capitalize_filter_spec.rb +14 -0
- data/spec/normatron/filters/chomp_filter_spec.rb +15 -0
- data/spec/normatron/filters/dasherize_filter_spec.rb +9 -0
- data/spec/normatron/filters/downcase_filter_spec.rb +10 -0
- data/spec/normatron/filters/dump_filter_spec.rb +10 -0
- data/spec/normatron/filters/keep_filter_spec.rb +86 -0
- data/spec/normatron/filters/remove_filter_spec.rb +86 -0
- data/spec/normatron/filters/squeeze_filter_spec.rb +10 -0
- data/spec/normatron/filters/squish_filter_spec.rb +12 -0
- data/spec/normatron/filters/strip_filter_spec.rb +12 -0
- data/spec/normatron/filters/swapcase_filter_spec.rb +12 -0
- data/spec/normatron/filters/titleize_filter_spec.rb +12 -0
- data/spec/normatron/filters/underscore_filter_spec.rb +26 -0
- data/spec/normatron/filters/upcase_filter_spec.rb +10 -0
- data/spec/normatron_spec.rb +28 -2
- data/spec/spec_helper.rb +37 -4
- data/spec/support/my_filters.rb +7 -0
- data/spec/support/user_model.rb +14 -0
- metadata +64 -13
- data/spec/configuration_spec.rb +0 -53
- data/spec/extensions/active_record_spec.rb +0 -114
- data/spec/filters_spec.rb +0 -442
- data/spec/support/model_model.rb +0 -3
- data/spec/support/schema.rb +0 -7
data/README.textile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
h1. Normatron
|
2
2
|
|
3
|
-
Normatron is
|
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
|
-
*
|
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 "
|
52
|
-
These filters are applied to all attributes
|
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
|
-
>
|
65
|
+
> memory = Product.create name: " memory card "
|
62
66
|
=> #<Product id: nil, name: "memory card", price: nil>
|
63
|
-
>
|
67
|
+
> null = Product.create name: " "
|
64
68
|
=> #<Product id: nil, name: nil, price: nil>
|
65
69
|
|
66
|
-
h3.
|
70
|
+
h3. Configurations
|
67
71
|
|
68
|
-
|
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
|
-
|
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.
|
76
|
+
h3. Applying normalizations
|
79
77
|
|
80
|
-
|
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.
|
84
|
-
=>
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
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.
|
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
|
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.
|
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 =>
|
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"]
|
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.
|
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.
|
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
|
208
|
-
* "normalize_attributes (normalize_attributes Gem)":https://github.com/fnando/normalize_attributes -
|
209
|
-
* "attribute_normalizer (attribute_normalizer Gem)":https://github.com/mdeering/attribute_normalizer -
|
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
|
-
|
26
|
+
=end
|
27
|
+
require 'rspec/core/rake_task'
|
26
28
|
|
27
|
-
|
28
|
-
|
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 => :
|
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
|
data/lib/normatron.rb
CHANGED
@@ -1,20 +1,34 @@
|
|
1
1
|
require "normatron/configuration"
|
2
|
-
require "normatron/extensions
|
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
|
11
|
+
def setup
|
14
12
|
yield(configuration)
|
15
13
|
end
|
16
|
-
alias :setup :configure
|
17
|
-
end
|
18
|
-
end
|
19
14
|
|
20
|
-
|
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
|
-
|
4
|
-
|
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 = {
|
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
|
27
|
-
|
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 =
|
35
|
+
@default_filters = Normatron.build_hash(filters)
|
32
36
|
end
|
33
37
|
end
|
34
38
|
end
|
@@ -1,16 +1,19 @@
|
|
1
|
-
require
|
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 :
|
13
|
+
before_validation :apply_normalizations
|
11
14
|
|
12
15
|
class << self
|
13
|
-
attr_accessor :
|
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
|
-
|
23
|
-
raise
|
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
|
32
|
+
new_filters = Normatron.build_hash(options[:with])
|
30
33
|
end
|
31
34
|
|
32
35
|
# Append to older filters hash
|
33
|
-
@
|
34
|
-
@
|
35
|
-
args.reduce(@
|
36
|
-
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
|
44
|
-
|
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
|
51
|
-
value =
|
55
|
+
elsif !named_filters[filter].nil?
|
56
|
+
value = named_filters[filter].evaluate(value, *args)
|
52
57
|
else
|
53
|
-
raise
|
58
|
+
raise UnknownFilterError
|
54
59
|
end
|
55
60
|
end
|
56
61
|
|
data/lib/normatron/filters.rb
CHANGED
@@ -1,384 +1,31 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require '
|
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
|
-
|
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
|