ordinary 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +232 -0
- data/Rakefile +1 -0
- data/lib/ordinary.rb +160 -0
- data/lib/ordinary/builder.rb +79 -0
- data/lib/ordinary/module.rb +117 -0
- data/lib/ordinary/normalizer.rb +46 -0
- data/lib/ordinary/unit.rb +85 -0
- data/lib/ordinary/version.rb +3 -0
- data/ordinary.gemspec +24 -0
- data/spec/ordinary/builder_spec.rb +137 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1bff706645249ee9b180396fa8dbd97c60de1a32
|
4
|
+
data.tar.gz: bef17919f5c25b578de54234e9b79d7611902a5b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c59a7bc904f9b0bcaa687dec7fa6b779cc2b257e615179086ad6d433bd9b320e296a1330950b2261052c10b7ee4b348c9a6ca3ca648d97e2a0ff3579df2db6f0
|
7
|
+
data.tar.gz: 544ffa6acca8a2412426b7f4c5dfc8d91a4c03437a4e66fe6be70a5735f049486ec7150a3f8ca25d1d0db5204f1fe23d6a9dbce7cacaaea85e9f3a3a9d47c020
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Takahiro Kondo
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
Ordinary
|
2
|
+
========
|
3
|
+
|
4
|
+
Normalizer for any model
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'ordinary'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install ordinary
|
20
|
+
|
21
|
+
Usage
|
22
|
+
-----
|
23
|
+
|
24
|
+
First, you will include `Ordinary` to the model of the object. Following is an example of using ActiveModel.
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'ordinary'
|
28
|
+
|
29
|
+
class Person
|
30
|
+
include ActiveModel::Model
|
31
|
+
include Ordinary
|
32
|
+
|
33
|
+
attr_accessor :name
|
34
|
+
|
35
|
+
normalizes :name do |value|
|
36
|
+
value.strip.squeeze(' ')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
You can get the normalized value with `#normalize_attribute` or `#normalized_ATTR_NAME`.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
person = Person.new(:name => ' Koyomi Araragi ')
|
45
|
+
puts person.name # => " Koyomi Araragi "
|
46
|
+
puts person.normalize_attribute(:name) # => "Koyomi Araragi"
|
47
|
+
puts person.normalized_name # => "Koyomi Araragi"
|
48
|
+
```
|
49
|
+
|
50
|
+
And you can get the normalized model with `#normalize`.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
normalized_person = person.normalize
|
54
|
+
puts normalized_person.normalized? # => true
|
55
|
+
puts normalized_person.name # => "Koyomi Araragi"
|
56
|
+
```
|
57
|
+
|
58
|
+
Off course, it doesn't affect the original model.
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
puts person.normalized? # => false
|
62
|
+
puts person.name # => " Koyomi Araragi "
|
63
|
+
```
|
64
|
+
|
65
|
+
However, if you use `#normalize!`, the original model will also be normalized.
|
66
|
+
|
67
|
+
How to define normalization
|
68
|
+
---------------------------
|
69
|
+
|
70
|
+
How to define normalization is from where you include `Ordinary`.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
require 'ordinary'
|
74
|
+
|
75
|
+
class AnyModel
|
76
|
+
include Ordinary
|
77
|
+
|
78
|
+
# define normalization...
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
Incidentally, in order to enable to read and write to a target attirbute, you must define `#ATTR_NAME` and `#ATTR_NAME=`.
|
83
|
+
|
84
|
+
Normalization defines with `.normalizes`.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
class AnyModel
|
88
|
+
# ...
|
89
|
+
|
90
|
+
attr_accessor :attr1, :attr2
|
91
|
+
|
92
|
+
normalizes :attr1, :attr2 do |value|
|
93
|
+
# process for normalization
|
94
|
+
end
|
95
|
+
|
96
|
+
# ...
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
You can define in a variety of ways.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
class AnyModel
|
104
|
+
# ...
|
105
|
+
|
106
|
+
attr_accessor :attr1, :attr2
|
107
|
+
|
108
|
+
# specify process with a block
|
109
|
+
normalizes :attr1 do |value|
|
110
|
+
"#{value}_1"
|
111
|
+
end
|
112
|
+
|
113
|
+
# if define normalization to same attribute, normalization runs in the order
|
114
|
+
# in which you defined
|
115
|
+
normalizes :attr1 do |value|
|
116
|
+
"#{value}_2"
|
117
|
+
end
|
118
|
+
|
119
|
+
# If specify Proc to last argument, define composed unit in the Proc as
|
120
|
+
# process of normalization (units described later)
|
121
|
+
normalizes :attr2, lambda { lstrip | rstrip }
|
122
|
+
|
123
|
+
# also specify both block and Proc (position of process of block decides by
|
124
|
+
# block unit)
|
125
|
+
normalizes :attr2, lambda { block | squeeze(' ') } do |value|
|
126
|
+
"#{value}_3"
|
127
|
+
end
|
128
|
+
|
129
|
+
# also specify options
|
130
|
+
normalizes :attr2, if: lambda { !attr2.nil? }, with: lambda { block | at(0) } do |value|
|
131
|
+
(value.empty? or %w(0 false f).include?(value)) ? 'false' : 'true'
|
132
|
+
end
|
133
|
+
|
134
|
+
# ...
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
How to create an units module
|
139
|
+
-----------------------------
|
140
|
+
|
141
|
+
You can create a module bundled some units. You'll use `Ordinary::Module` to do so.
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
require 'ordinary/module'
|
145
|
+
|
146
|
+
module AnyModule
|
147
|
+
extend Ordinary::Module
|
148
|
+
|
149
|
+
# define the module...
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
And you can register to use the module with `Ordinary.register`.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
require 'ordinary'
|
157
|
+
|
158
|
+
Ordinary.register(AnyModule)
|
159
|
+
```
|
160
|
+
|
161
|
+
### Define an unit
|
162
|
+
|
163
|
+
An unit can define with `.unit` in the module.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
module AnyModule
|
167
|
+
# ...
|
168
|
+
|
169
|
+
unit :some_unit do |value|
|
170
|
+
# process for the unit...
|
171
|
+
end
|
172
|
+
|
173
|
+
# ...
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
You can define in a variety of ways.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
module AnyModule
|
181
|
+
# ...
|
182
|
+
|
183
|
+
# specify process with a block
|
184
|
+
unit :lstrip do |value|
|
185
|
+
value.lstrip
|
186
|
+
end
|
187
|
+
|
188
|
+
# okay as the argument
|
189
|
+
unit :rstrip, lambda { |value| value.rstrip }
|
190
|
+
|
191
|
+
# actually, above examples are okay at follows
|
192
|
+
unit :lstrip
|
193
|
+
|
194
|
+
# as aliasing
|
195
|
+
unit :ltrim, :lstrip
|
196
|
+
|
197
|
+
# by the way, units are defined as module functions, you can also see
|
198
|
+
p lstrip # => #<Ordinary::Unit:0x0x007ff6ec8e7610 AnyModule#lstrip>
|
199
|
+
|
200
|
+
# and compose units by #| (or #>>, #<<)
|
201
|
+
unit :strip, lstrip | rstrip
|
202
|
+
|
203
|
+
# ...
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
### Define a dependency
|
208
|
+
|
209
|
+
If exist dependencies to some libraries to units in the module, will resolve with `.requires`.
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
module AnyModule
|
213
|
+
# ...
|
214
|
+
|
215
|
+
requires 'nkf'
|
216
|
+
|
217
|
+
unit :to_half do |value|
|
218
|
+
NKF.nkf('-wWZ1', value)
|
219
|
+
end
|
220
|
+
|
221
|
+
# ...
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
Contributing
|
226
|
+
------------
|
227
|
+
|
228
|
+
1. Fork it
|
229
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
230
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
231
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
232
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/lib/ordinary.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'ordinary/version'
|
2
|
+
require 'ordinary/builder'
|
3
|
+
require 'ordinary/normalizer'
|
4
|
+
require 'ordinary/module'
|
5
|
+
|
6
|
+
module Ordinary
|
7
|
+
|
8
|
+
# Normalize an attribute.
|
9
|
+
#
|
10
|
+
# @param [Symbol] attr_name an attribute to normalize
|
11
|
+
# @param [Symbol] context normalization context. defaults to nil
|
12
|
+
# @return [Object] the normalized attribute
|
13
|
+
def normalize_attribute(attr_name, context = nil)
|
14
|
+
value = __send__(attr_name)
|
15
|
+
|
16
|
+
unless value.nil?
|
17
|
+
self.class.normalizers[attr_name.to_sym].each do |normalizer|
|
18
|
+
next unless normalizer.coming_under?(self)
|
19
|
+
next unless normalizer.run_at?(context)
|
20
|
+
value = normalizer.normalize(value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
value
|
25
|
+
end
|
26
|
+
|
27
|
+
# Normalize all attributes and return the normalized object.
|
28
|
+
#
|
29
|
+
# @param [Symbol] context normalization context. defaults to nil
|
30
|
+
# @return [Object] the normalized object
|
31
|
+
def normalize(context = nil)
|
32
|
+
clone.normalize!(context)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Normalize all attributes distructively.
|
36
|
+
#
|
37
|
+
# @param [Symbol] context normalization context. defaults to nil
|
38
|
+
# @return [Object] self
|
39
|
+
def normalize!(context = nil)
|
40
|
+
unless normalized?(context)
|
41
|
+
self.class.normalizers.keys.each do |attr_name|
|
42
|
+
__send__(:"#{attr_name}=", normalize_attribute(attr_name, context))
|
43
|
+
end
|
44
|
+
|
45
|
+
@normalization_context = context
|
46
|
+
end
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Determine if self is normalized.
|
52
|
+
#
|
53
|
+
# @param [Symbol] context normalization context. defaults to nil
|
54
|
+
# @return whether self is normalized
|
55
|
+
def normalized?(context = nil)
|
56
|
+
return false unless instance_variable_defined?(:@normalization_context)
|
57
|
+
@normalization_context.nil? or (@normalization_context == context)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Register modules to the context of building.
|
61
|
+
#
|
62
|
+
# @scope class
|
63
|
+
# @param [Array<Ordinary::Module>] modules modules to register
|
64
|
+
def self.register(*modules)
|
65
|
+
Builder::Context.register(*modules)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Unregister modules from the context of building.
|
69
|
+
#
|
70
|
+
# @scope class
|
71
|
+
# @param [Array<Ordinary::Module>] modules modules to unregister
|
72
|
+
def self.unregister(modules)
|
73
|
+
Builder::Context.unregister(*modules)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.included(klass)
|
77
|
+
klass.extend(ClassMethods)
|
78
|
+
|
79
|
+
if defined?(ActiveModel::Validations) and klass.include?(ActiveModel::Validations)
|
80
|
+
method = klass.instance_method(:run_validations!)
|
81
|
+
|
82
|
+
klass.__send__(:define_method, :run_validations!) do
|
83
|
+
context = validation_context
|
84
|
+
normalized?(context) ? method.bind(self).call : normalize(context).valid?(context)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
if defined?(ActiveRecord::Base) and klass.include?(ActiveRecord::Base)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
module ClassMethods
|
93
|
+
|
94
|
+
# @attribute [r] normalizers
|
95
|
+
# @return [Hash<Symbol, Array<Ordinary::Normalizer>>] normalizers for each
|
96
|
+
# attribute
|
97
|
+
def normalizers
|
98
|
+
@normalizers ||= {}
|
99
|
+
end
|
100
|
+
|
101
|
+
# Define normalization for attributes.
|
102
|
+
#
|
103
|
+
# @example define normalization with a builder for the normalizer
|
104
|
+
#
|
105
|
+
# normalizes :name, lambda { lstrip }
|
106
|
+
# normalizes :name, lambda { lstrip | rstrip }
|
107
|
+
#
|
108
|
+
# @example define normalization with a block
|
109
|
+
#
|
110
|
+
# normalizes :name do |value|
|
111
|
+
# value.squeeze(' ')
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# @example define normalization with a builder for the normalizer and a block
|
115
|
+
#
|
116
|
+
# normalizes :name, -> { lstrip | block | rstrip } do |value|
|
117
|
+
# value.squeeze(' ')
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# @param [Array<Symbol>] attr_names attirubte names to normalize
|
121
|
+
# @yield [value] normalize the attribute
|
122
|
+
# @yieldparam [Object] value value of the attribute to normalize
|
123
|
+
def normalizes(*attr_names, &block)
|
124
|
+
attr_names = attr_names.dup
|
125
|
+
buil = nil
|
126
|
+
options = {}
|
127
|
+
|
128
|
+
case attr_names.last
|
129
|
+
when Proc
|
130
|
+
build = attr_names.pop
|
131
|
+
when Hash
|
132
|
+
options = attr_names.pop.dup
|
133
|
+
build = options.delete(:with)
|
134
|
+
end
|
135
|
+
|
136
|
+
unless build or block
|
137
|
+
raise ArgumentError, 'process for building a normalizer' \
|
138
|
+
'(with the last argument or :with option) or ' \
|
139
|
+
'an unit of a normalizer ' \
|
140
|
+
'(with block) are not given'
|
141
|
+
end
|
142
|
+
|
143
|
+
build ||= lambda { block }
|
144
|
+
unit = Builder.new(&block).build(&build)
|
145
|
+
normalizer = Normalizer.new(options, &unit)
|
146
|
+
|
147
|
+
attr_names.each do |attr_name|
|
148
|
+
raise ArgumentError, "##{attr_name} is not defined" unless method_defined?(attr_name)
|
149
|
+
raise ArgumentError, "##{attr_name}= is not defined" unless method_defined?(:"#{attr_name}=")
|
150
|
+
|
151
|
+
(normalizers[attr_name.to_sym] ||= []) << normalizer
|
152
|
+
|
153
|
+
define_method :"normalized_#{attr_name}" do |context = nil|
|
154
|
+
normalize_attribute(attr_name, context)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'ordinary/unit'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Ordinary
|
5
|
+
class Builder
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
@context = Context.new(block)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @attribute [r] context
|
12
|
+
# @return [Ordinary::Builder::Context] the context of building
|
13
|
+
attr_reader :context
|
14
|
+
|
15
|
+
# Build units for a normalizer.
|
16
|
+
#
|
17
|
+
# @yield build units for a normalizer
|
18
|
+
# @return [Ordinary::Unit, Ordinary::Units] units for a normalizer
|
19
|
+
def build(&build)
|
20
|
+
@context.instance_exec(&build)
|
21
|
+
end
|
22
|
+
|
23
|
+
module Context
|
24
|
+
class << self
|
25
|
+
attr_reader :current, :modules
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.update(modules)
|
29
|
+
@current = Class.new { include *[*modules, Context] }.freeze
|
30
|
+
@modules = modules.freeze
|
31
|
+
end
|
32
|
+
private_class_method :update
|
33
|
+
|
34
|
+
update(Set.new)
|
35
|
+
|
36
|
+
def self.register(*modules)
|
37
|
+
update(@modules | modules)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.unregister(*modules)
|
41
|
+
update(@modules - modules)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.new(*args, &block)
|
45
|
+
@current.new(*args, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(block = nil)
|
49
|
+
@block = block ? Unit.new(nil, &block) : nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def block
|
53
|
+
unless @block
|
54
|
+
e = BlockNotGiven.new("`block' unit cannot use if a block is not given")
|
55
|
+
e.set_backtrace(caller)
|
56
|
+
raise e
|
57
|
+
end
|
58
|
+
|
59
|
+
@block
|
60
|
+
end
|
61
|
+
|
62
|
+
def method_missing(method_name, *args, &block)
|
63
|
+
e = UnitNotDefined.new("`#{method_name}' unit is not defined")
|
64
|
+
e.set_backtrace(caller)
|
65
|
+
raise e
|
66
|
+
end
|
67
|
+
|
68
|
+
def inspect
|
69
|
+
header = "#{Context.name}:0x%014x" % (object_id << 1)
|
70
|
+
module_list = Context.modules.map(&:name).sort * ', '
|
71
|
+
with_block = @block ? ' with a block' : ''
|
72
|
+
"#<#{header} [#{module_list}]#{with_block}>"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class UnitNotDefined < StandardError; end
|
77
|
+
class BlockNotGiven < StandardError; end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'ordinary/unit'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Ordinary
|
5
|
+
module Module
|
6
|
+
|
7
|
+
# @attribute [r] requirements
|
8
|
+
# @return [Ordinary::Module::Requirements] libraries that the module
|
9
|
+
# requires
|
10
|
+
def requirements
|
11
|
+
@requirements ||= Requirements.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add libraries to the requirements.
|
15
|
+
#
|
16
|
+
# @param [Array<String>] libraries required libraries
|
17
|
+
#
|
18
|
+
# @see Ordinary::Module#requirements
|
19
|
+
def requires(*libraries)
|
20
|
+
requirements.add(*libraries)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Define an unit for some normalizer.
|
24
|
+
#
|
25
|
+
# @example define an unit with a block
|
26
|
+
#
|
27
|
+
# unit :lstrip do |value|
|
28
|
+
# value.lstrip
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # same as above
|
32
|
+
# unit :lstrip, lambda { |value| value.lstrip }
|
33
|
+
#
|
34
|
+
# @example define an unit simply
|
35
|
+
#
|
36
|
+
# # call .lstrip of a value
|
37
|
+
# unit :lstrip
|
38
|
+
#
|
39
|
+
# # and named "ltrim"
|
40
|
+
# unit :ltrim, :lstrip
|
41
|
+
#
|
42
|
+
# @example define an unit with existing units
|
43
|
+
#
|
44
|
+
# unit :lstrip
|
45
|
+
# unit :rstrip
|
46
|
+
#
|
47
|
+
# # use an existing unit
|
48
|
+
# unit :ltrim, lstrip
|
49
|
+
# unit :rtrim, rstrip
|
50
|
+
#
|
51
|
+
# # use by combining existing units (by #|, #>> or #<<)
|
52
|
+
# unit :trim, ltrim | rtrim
|
53
|
+
#
|
54
|
+
# @param [Symbol] name name of the unit
|
55
|
+
# @param [Symbol] unit
|
56
|
+
# @param [Proc] unit process of normalization that the unit plays
|
57
|
+
# @param [Ordinary::Unit, Ordinary::Units] unit an existing unit or
|
58
|
+
# combination existing units
|
59
|
+
# @yield [value, *args] process of normalization that the unit plays
|
60
|
+
# @yieldparam [Object] value a value to process
|
61
|
+
# @yieldparam [Array<Object>] args additional arguments
|
62
|
+
def unit(name, unit = nil, &block)
|
63
|
+
unit = unit.to_sym if unit.is_a?(String)
|
64
|
+
|
65
|
+
unit = if unit.nil? and block.nil?
|
66
|
+
unit_by_send(name)
|
67
|
+
elsif unit.is_a?(Symbol)
|
68
|
+
unit_by_send(unit)
|
69
|
+
elsif unit.is_a?(Proc)
|
70
|
+
create_unit(&unit)
|
71
|
+
elsif block_given?
|
72
|
+
create_unit(&block)
|
73
|
+
else
|
74
|
+
unit
|
75
|
+
end
|
76
|
+
|
77
|
+
unit.owned_by(self, name) unless unit.owned?
|
78
|
+
define_method(name) { |*args| args.empty? ? unit : unit.with(*args) }
|
79
|
+
module_function name
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def unit_by_send(method_name)
|
85
|
+
create_unit { |value, *args| value.__send__(method_name, *args) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_unit(&process)
|
89
|
+
Unit.new(nil, requirements, &process)
|
90
|
+
end
|
91
|
+
|
92
|
+
class Requirements
|
93
|
+
def initialize
|
94
|
+
@libraries = Set.new
|
95
|
+
@loaded = false
|
96
|
+
end
|
97
|
+
|
98
|
+
def add(*libraries)
|
99
|
+
@loaded &= !(Set.new(libraries) - @libraries).empty?
|
100
|
+
@libraries |= libraries
|
101
|
+
end
|
102
|
+
|
103
|
+
def delete(*libraries)
|
104
|
+
@libraries -= libraries
|
105
|
+
end
|
106
|
+
|
107
|
+
def loaded?
|
108
|
+
@loaded
|
109
|
+
end
|
110
|
+
|
111
|
+
def load
|
112
|
+
@libraries.each(&method(:require))
|
113
|
+
@loaded = true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Ordinary
|
2
|
+
class Normalizer
|
3
|
+
|
4
|
+
def initialize(options = {}, &process)
|
5
|
+
@determine = extract_determiner(options)
|
6
|
+
@context = options[:on]
|
7
|
+
@process = process
|
8
|
+
end
|
9
|
+
|
10
|
+
# Normalize a value by the normalizer.
|
11
|
+
#
|
12
|
+
# @param [Object] value a value to normalize
|
13
|
+
# @return [Object] a normalized value
|
14
|
+
def normalize(value)
|
15
|
+
@process.call(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Determine if a model coming under a target of the normalizer.
|
19
|
+
#
|
20
|
+
# @param [ActiveModel::Model] model a model to determine if be a target of
|
21
|
+
# the normalizer
|
22
|
+
# @return whether the model is a target of the normalizer
|
23
|
+
def coming_under?(model)
|
24
|
+
@determine.nil? or !!@determine.call(model)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Determine if
|
28
|
+
#
|
29
|
+
# @param [Symbol] context a context to determine
|
30
|
+
# @return whether
|
31
|
+
def run_at?(context)
|
32
|
+
@context.nil? or (@context == context)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def extract_determiner(options)
|
38
|
+
if determine = options[:if]
|
39
|
+
lambda { |model| model.instance_eval(&determine) }
|
40
|
+
elsif determine = options[:unless]
|
41
|
+
lambda { |model| !model.instance_eval(&determine) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Ordinary
|
2
|
+
module Composable
|
3
|
+
def >>(other)
|
4
|
+
Units.new([*self, *other])
|
5
|
+
end
|
6
|
+
alias | >>
|
7
|
+
|
8
|
+
def <<(other)
|
9
|
+
other >> self
|
10
|
+
end
|
11
|
+
|
12
|
+
def owner
|
13
|
+
owned? ? "#{@module.name}##{@name}" : 'owner unknown'
|
14
|
+
end
|
15
|
+
|
16
|
+
def owned_by(mod, name)
|
17
|
+
@module = mod
|
18
|
+
@name = name
|
19
|
+
end
|
20
|
+
|
21
|
+
def owned?
|
22
|
+
@module and @name
|
23
|
+
end
|
24
|
+
|
25
|
+
def instance_id
|
26
|
+
"#{self.class.name}:0x%014x" % (object_id << 1)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Unit
|
31
|
+
include Composable
|
32
|
+
|
33
|
+
def initialize(original_unit, requirements = nil, arguments = [], &process)
|
34
|
+
raise ArgumentError, 'block not supplied' unless block_given?
|
35
|
+
@original_unit = original_unit
|
36
|
+
@requirements = requirements
|
37
|
+
@arguments = arguments
|
38
|
+
@process = process
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :requirements
|
42
|
+
|
43
|
+
attr_reader :arguments
|
44
|
+
|
45
|
+
attr_reader :process
|
46
|
+
|
47
|
+
def with(*arguments)
|
48
|
+
self.class.new(self, @requirements, arguments, &@process)
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_proc
|
52
|
+
@requirements.load if @requirements and !@requirements.loaded?
|
53
|
+
args = @arguments + (@original_unit ? @original_unit.arguments : [])
|
54
|
+
lambda { |value| @process.call(value, *args) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
original_owner = ''
|
59
|
+
|
60
|
+
if @original_unit
|
61
|
+
argument_list = @arguments.map(&:inspect) * ', '
|
62
|
+
original_owner = " (#{@original_unit.owner} with [#{argument_list}])"
|
63
|
+
end
|
64
|
+
|
65
|
+
"#<#{instance_id} #{owner}#{original_owner}>"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Units < Array
|
70
|
+
include Composable
|
71
|
+
|
72
|
+
def with(*arguments)
|
73
|
+
self.class.new(map { |unit| unit.with(*arguments) })
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_proc
|
77
|
+
processes = map(&:to_proc)
|
78
|
+
lambda { |value| processes.reduce(value) { |v, p| p.call(v) } }
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
"#<#{instance_id} #{owner} [#{map(&:inspect) * ', '}]>"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/ordinary.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ordinary/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'ordinary'
|
8
|
+
spec.version = Ordinary::VERSION
|
9
|
+
spec.authors = ['Takahiro Kondo']
|
10
|
+
spec.email = ['heartery@gmail.com']
|
11
|
+
spec.description = %q{It normalizes nondistructively specified attributes of any model}
|
12
|
+
spec.summary = %q{Normalizer for any model}
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'rake'
|
22
|
+
spec.add_development_dependency 'rspec'
|
23
|
+
spec.add_development_dependency 'activemodel'
|
24
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'ordinary/builder'
|
2
|
+
|
3
|
+
describe Ordinary::Builder do
|
4
|
+
describe '#context' do
|
5
|
+
subject { described_class.new { }.context }
|
6
|
+
|
7
|
+
it { should be_an(Ordinary::Builder::Context.current) }
|
8
|
+
its(:block) { should_not be_nil }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#build' do
|
12
|
+
it '' do
|
13
|
+
end
|
14
|
+
|
15
|
+
context '' do
|
16
|
+
# it '' do
|
17
|
+
# build = lambda { a }
|
18
|
+
# builder = Ordinary::Builder.new(build)
|
19
|
+
# builder.build
|
20
|
+
# end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe Ordinary::Builder::Context do
|
26
|
+
before { described_class.__send__(:update, Set.new) }
|
27
|
+
|
28
|
+
def change_for_current_ancestors(*modules)
|
29
|
+
change(described_class, :current) do
|
30
|
+
described_class.current.ancestors.select { |mod| modules.include?(mod) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def change_for_modules
|
35
|
+
change(described_class, :modules)
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.register' do
|
39
|
+
it do
|
40
|
+
expect {
|
41
|
+
described_class.register(Math)
|
42
|
+
}.to change_for_current_ancestors(Math).from([]).to([Math])
|
43
|
+
end
|
44
|
+
|
45
|
+
it do
|
46
|
+
expect {
|
47
|
+
described_class.register(Math)
|
48
|
+
}.to change_for_modules.from(Set.new).to(Set.new([Math]))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '.unregister' do
|
53
|
+
before { described_class.register(Math) }
|
54
|
+
|
55
|
+
it do
|
56
|
+
expect {
|
57
|
+
described_class.unregister(Math)
|
58
|
+
}.to change_for_current_ancestors(Math).from([Math]).to([])
|
59
|
+
end
|
60
|
+
|
61
|
+
it do
|
62
|
+
expect {
|
63
|
+
described_class.unregister(Math)
|
64
|
+
}.to change_for_modules.from(Set.new([Math])).to(Set.new)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '.new' do
|
69
|
+
before do
|
70
|
+
@original = described_class.current
|
71
|
+
described_class.instance_variable_set(:@current, Class.new)
|
72
|
+
end
|
73
|
+
|
74
|
+
after do
|
75
|
+
described_class.instance_variable_set(:@current, @original)
|
76
|
+
end
|
77
|
+
|
78
|
+
subject { described_class.new }
|
79
|
+
|
80
|
+
it { should be_a(described_class.current) }
|
81
|
+
|
82
|
+
it "should call #{described_class}.current.new with same arguments and same block" do
|
83
|
+
described_class.current.should_receive(:new).with('arg1', 'arg2').and_yield
|
84
|
+
described_class.new('arg1', 'arg2') { }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#block' do
|
89
|
+
let (:sample_block) { lambda { } }
|
90
|
+
|
91
|
+
subject { context.block }
|
92
|
+
|
93
|
+
context 'with a block at construction' do
|
94
|
+
let (:context) { described_class.new(sample_block) }
|
95
|
+
|
96
|
+
it { should be_an(Ordinary::Unit) }
|
97
|
+
its(:process) { should be(sample_block) }
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'with no block at construction' do
|
101
|
+
let (:context) { described_class.new }
|
102
|
+
|
103
|
+
it { expect { subject }.to raise_error(Ordinary::Builder::BlockNotGiven) }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '#undefined_method' do
|
108
|
+
it do
|
109
|
+
expect {
|
110
|
+
described_class.new.undefined_method
|
111
|
+
}.to raise_error(Ordinary::Builder::UnitNotDefined, "`undefined_method' unit is not defined")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#inspect' do
|
116
|
+
let (:header) { "#{described_class.name}:0x%014x" % (context.object_id << 1) }
|
117
|
+
let (:module_list) { modules.map(&:name).sort * ', ' }
|
118
|
+
let (:modules) { [Math, Enumerable] }
|
119
|
+
|
120
|
+
subject { context.inspect }
|
121
|
+
|
122
|
+
before { described_class.register(*modules) }
|
123
|
+
after { described_class.unregister(*modules) }
|
124
|
+
|
125
|
+
context 'with a block at construction' do
|
126
|
+
let (:context) { described_class.new(lambda { }) }
|
127
|
+
|
128
|
+
it { should be == "#<#{header} [#{module_list}] with a block>" }
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'with no block at construction' do
|
132
|
+
let (:context) { described_class.new }
|
133
|
+
|
134
|
+
it { should be == "#<#{header} [#{module_list}]>" }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ordinary
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Takahiro Kondo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activemodel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: It normalizes nondistructively specified attributes of any model
|
56
|
+
email:
|
57
|
+
- heartery@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/ordinary.rb
|
68
|
+
- lib/ordinary/builder.rb
|
69
|
+
- lib/ordinary/module.rb
|
70
|
+
- lib/ordinary/normalizer.rb
|
71
|
+
- lib/ordinary/unit.rb
|
72
|
+
- lib/ordinary/version.rb
|
73
|
+
- ordinary.gemspec
|
74
|
+
- spec/ordinary/builder_spec.rb
|
75
|
+
homepage: ''
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.0.3
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Normalizer for any model
|
99
|
+
test_files:
|
100
|
+
- spec/ordinary/builder_spec.rb
|