moonshine 0.2.1.pre → 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.
- checksums.yaml +4 -4
- data/README.md +324 -10
- data/lib/moonshine.rb +2 -2
- data/lib/moonshine/base.rb +20 -25
- data/lib/moonshine/dsl.rb +22 -0
- data/lib/moonshine/filter.rb +24 -21
- data/lib/moonshine/version.rb +1 -1
- data/moonshine.gemspec +2 -2
- data/test/lib/moonshine/base_test.rb +39 -66
- data/test/lib/moonshine/filter_test.rb +111 -64
- data/test/lib/moonshine/version_test.rb +2 -2
- data/test/lib/moonshine_test.rb +37 -0
- data/test/support/empty_tale_generator.rb +2 -0
- data/test/support/tale.rb +39 -0
- data/test/support/tale_generator.rb +7 -0
- data/test/support/tale_transform.rb +6 -0
- data/test/test_helper.rb +4 -0
- metadata +34 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e1cbffb31dd0dcced747bb06c3cc6a6370599e6e
|
4
|
+
data.tar.gz: e8a3183d13be8f733e2d85fbd12cb95b336d3c8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc85d07677d1a4a4e634086e3c471e62552eb22e73c0ff063709b16aaf62d38ae915fb3b2c8198b95852f0e0d64c2d4273f90f1fedef4c781185128b751ff629
|
7
|
+
data.tar.gz: 14ab17f3c26783a930ffdd66dca33fbcfe0685bb03d2fd60acab3b8e602c7c622420ad182a02080717def56c05072ce20632f0b10f74b0936d7601a0390a81f1
|
data/README.md
CHANGED
@@ -1,24 +1,338 @@
|
|
1
|
-
# Moonshine
|
1
|
+
# Moonshine
|
2
|
+
[](http://badge.fury.io/rb/moonshine)
|
3
|
+
[](https://travis-ci.org/nebulab/moonshine)
|
4
|
+
[](https://coveralls.io/r/nebulab/moonshine?branch=master)
|
5
|
+
[](https://codeclimate.com/github/nebulab/moonshine)
|
2
6
|
|
3
|
-
|
7
|
+
Moonshine is a configuration driven method chain builder.
|
4
8
|
|
5
|
-
|
9
|
+
Usually writing a conditional method chain requires a lot of if and case
|
10
|
+
statements that increase code complexity, reduce code readability and make
|
11
|
+
testing hard. Moonshine removes this complexity by providing a way to call a
|
12
|
+
list of methods based on some input parameters. A completely object-oriented
|
13
|
+
approach also ensures an easily testable interface.
|
14
|
+
|
15
|
+
## Why Moonshine?
|
16
|
+
|
17
|
+
Moonshine has been built to solve a problem which is particularly obvious when
|
18
|
+
building complex REST APIs. A good API usually filters data based on parameters
|
19
|
+
passed via a GET or POST request, from a Rails point of view controller
|
20
|
+
`params[]` are used to filter down data kept in `ActiveRecord` models.
|
21
|
+
|
22
|
+
Usually to achieve this task you use [Ransack](https://github.com/activerecord-hackery/ransack),
|
23
|
+
or [HasScope](https://github.com/plataformatec/has_scope) or some hacky solution
|
24
|
+
[like this](http://stackoverflow.com/questions/1658990/one-or-more-params-in-model-find-conditions-with-ruby-on-rails).
|
25
|
+
With Moonshine you can do similar things without being restricted to using
|
26
|
+
`ActiveRecord` and with code which is easy to test.
|
27
|
+
|
28
|
+
A real world example with a Rails REST API is best to explain what Moonshine's
|
29
|
+
power is. If you define a class like this:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class PostFilter < Moonshine::Base
|
33
|
+
subject -> { Post }
|
34
|
+
|
35
|
+
param :category
|
36
|
+
param :with_tags
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
Then in your controller you can:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
class PostController < ApplicationController
|
44
|
+
def index
|
45
|
+
@posts = PostFilter.new(params).run
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
So when you receive a request like this:
|
6
51
|
|
7
|
-
|
52
|
+
```
|
53
|
+
GET http://my.awesome-blog.com/posts?category=cats&with_tags=cute,lovely
|
54
|
+
```
|
8
55
|
|
9
|
-
|
56
|
+
Moonshine creates a method chain based on the parameters passed. In this case
|
57
|
+
the `category` and `with_tags` methods are called on the `Post` class with
|
58
|
+
`cats` and `cute,lovely` as argument values.
|
10
59
|
|
11
|
-
|
60
|
+
## Installation
|
61
|
+
|
62
|
+
As usual you can install it using [Bundler](http://bundler.io) by adding it
|
63
|
+
to your application's Gemfile:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
gem 'moonshine'
|
67
|
+
```
|
68
|
+
|
69
|
+
And then executing:
|
12
70
|
|
13
|
-
|
71
|
+
```ruby
|
72
|
+
$ bundle
|
73
|
+
```
|
14
74
|
|
15
|
-
Or install it yourself
|
75
|
+
Or you can install it yourself by running:
|
16
76
|
|
17
|
-
|
77
|
+
```ruby
|
78
|
+
$ gem install moonshine
|
79
|
+
```
|
18
80
|
|
19
81
|
## Usage
|
20
82
|
|
21
|
-
|
83
|
+
Now we'll take a look at how we can use Moonshine in a Rails application.
|
84
|
+
Moonshine supports any kind of object but probably its advantages on a Rails
|
85
|
+
application are more obvious so we'll start with a quick example with Rails.
|
86
|
+
|
87
|
+
Let's pretend we have an ActiveRecord model like this:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
#
|
91
|
+
# The schema
|
92
|
+
#
|
93
|
+
create_table :posts do |t|
|
94
|
+
t.string :title
|
95
|
+
t.text :description
|
96
|
+
t.boolean :published
|
97
|
+
|
98
|
+
t.timestamps
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# The model
|
103
|
+
#
|
104
|
+
class Post < ActiveRecord::Base
|
105
|
+
scope :title_starts, -> (title) { where('title LIKE ?', "#{title}%") }
|
106
|
+
scope :desc_like, -> (desc) { where('description LIKE ?', "%#{desc}%") }
|
107
|
+
scope :created_at, -> (date) { where(created_at: date) }
|
108
|
+
scope :published, -> { where( published: true ) }
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
we can add a `PostQuery` class inherited from `Moonshine::Base` somewhere in
|
113
|
+
our Rails application (for example in the `/lib` directory) to manage method
|
114
|
+
chaining, in this case we are mostly chaining scopes:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
class PostQuery < Moonshine::Base
|
118
|
+
subject -> { Post }
|
119
|
+
|
120
|
+
param :title_starts
|
121
|
+
param :description_has, call: :desc_like
|
122
|
+
param :published, as_boolean: true
|
123
|
+
param :created_at, transform: :string_to_date
|
124
|
+
param :limit, default: 10
|
125
|
+
|
126
|
+
param :in_season do |subject, season|
|
127
|
+
date_range = case season
|
128
|
+
when :summer then Date.parse('2014/06/01')..Date.parse('2014/08/31')
|
129
|
+
when :winter then Date.parse('2014/12/01')..Date.parse('2014/02/28')
|
130
|
+
when :autumn then Date.parse('2014/09/01')..Date.parse('2014/11/30')
|
131
|
+
when :spring then Date.parse('2014/03/01')..Date.parse('2014/05/30')
|
132
|
+
end
|
133
|
+
|
134
|
+
subject.where( created_at: date_range )
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.string_to_date(string_date)
|
138
|
+
string_date.to_date
|
139
|
+
end
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
### Running the chain
|
144
|
+
|
145
|
+
After defining the `PostQuery` class we can run method chains with it on the
|
146
|
+
specified `subject`. An example run is like this:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
PostQuery.new({ title_starts: 'moonshine', in_season: :summer }).run
|
150
|
+
```
|
151
|
+
|
152
|
+
In the end we'll have the result of the execution of the method chain on the
|
153
|
+
`subject` object. In this case we'll have the `ActiveRecord::Relation` returned
|
154
|
+
by the various scopes being called on `Post`. In case Moonshine has to run an
|
155
|
+
empty chain (for example when no params are passed to it) the `subject` will be
|
156
|
+
returned.
|
157
|
+
|
158
|
+
### Configuring the chain
|
159
|
+
|
160
|
+
Let's take a look at each line of code to understand what Moonshine is all
|
161
|
+
about.
|
162
|
+
|
163
|
+
### Subject
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
subject -> { Post }
|
167
|
+
```
|
168
|
+
|
169
|
+
The subject is what the chain will be called on. It must be a block, proc or
|
170
|
+
lambda. When you run a method chain each method will be called on the `subject`
|
171
|
+
(which is evaluated at every run) and the result of the chain of methods called
|
172
|
+
will be the returned value.
|
173
|
+
|
174
|
+
In our Rails example every method or scope which is added to the chain will be
|
175
|
+
called, in order, on the `Post` subject. Since we're talking scopes here you can
|
176
|
+
see where this is going, Moonshine will build a long list of scopes and call it
|
177
|
+
for you.
|
178
|
+
|
179
|
+
### Param
|
180
|
+
|
181
|
+
The basic parameter is without arguments, when the chain is run it will look for
|
182
|
+
a method defined on the `subject` and call it with given parameter value.
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
param :title_starts
|
186
|
+
```
|
187
|
+
|
188
|
+
Calling the chain like this:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
PostQuery.new({ title_starts: 'moonshine' }).run
|
192
|
+
```
|
193
|
+
|
194
|
+
will run a `title_starts` scope on the `Post` model with 'moonshine' as an
|
195
|
+
argument.
|
196
|
+
|
197
|
+
### call
|
198
|
+
|
199
|
+
When the `subject` doesn't have a method named after the `param` argument,
|
200
|
+
you can add `call` to specify the actual method to call.
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
param :description_has, call: :desc_like
|
204
|
+
```
|
205
|
+
|
206
|
+
Calling the chain like this:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
PostQuery.new({ description_has: 'cool stuff!' }).run
|
210
|
+
```
|
211
|
+
|
212
|
+
will run the `desc_like` scope on the `Post` model with 'cool stuff!' as an
|
213
|
+
argument.
|
214
|
+
|
215
|
+
### as_boolean
|
216
|
+
|
217
|
+
When a method doesn't take any arguments you can add it to the method chain by
|
218
|
+
setting the `as_boolean` to `true`. This will make Moonshine call the method
|
219
|
+
based on the value passed to the `PostQuery` object.
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
param :published, as_boolean: true
|
223
|
+
```
|
224
|
+
|
225
|
+
This means that calling a chain like this:
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
PostQuery.new({ published: true }).run
|
229
|
+
```
|
230
|
+
|
231
|
+
will end up running the `published` scope on the `Post` model. In case it was
|
232
|
+
`published: false` Moonshine would have just returned `Post` since the chain
|
233
|
+
would be empty (no method needed to be called).
|
234
|
+
|
235
|
+
### default
|
236
|
+
|
237
|
+
If you need a method in the chain to return a default value you can use the
|
238
|
+
`default` option. As you would expect this would return the default value when
|
239
|
+
the chain is run whithout that parameter in the chain.
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
param :limit, default: 10
|
243
|
+
```
|
244
|
+
|
245
|
+
### transform
|
246
|
+
|
247
|
+
At times you may need to transform the values you are passing to the chain for
|
248
|
+
example when reading `params[]` you may need to transform something from string
|
249
|
+
to whatever you need in your model. In such occasions you can use the `trasform`
|
250
|
+
option.
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
param :created_at, transform: :string_to_date
|
254
|
+
|
255
|
+
def self.string_to_date(string_date)
|
256
|
+
string_date.to_date
|
257
|
+
end
|
258
|
+
```
|
259
|
+
|
260
|
+
So a run like:
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
PostQuery.new({ created_at: '2014/06/01' }).run
|
264
|
+
```
|
265
|
+
|
266
|
+
will end up using the `created_at` scope and passing in the actual `Date`
|
267
|
+
object.
|
268
|
+
|
269
|
+
### block
|
270
|
+
|
271
|
+
When total customization needs to be achieved and you don't feel adding more
|
272
|
+
code to the `Post` model, you can pass a block to execute that block instead of
|
273
|
+
any other method.
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
param :in_season do |subject, season|
|
277
|
+
date_range = case season
|
278
|
+
when :summer then Date.parse('2014/06/01')..Date.parse('2014/08/31')
|
279
|
+
when :winter then Date.parse('2014/12/01')..Date.parse('2014/02/28')
|
280
|
+
when :autumn then Date.parse('2014/09/01')..Date.parse('2014/11/30')
|
281
|
+
when :spring then Date.parse('2014/03/01')..Date.parse('2014/05/30')
|
282
|
+
end
|
283
|
+
|
284
|
+
subject.where( created_at: date_range )
|
285
|
+
end
|
286
|
+
```
|
287
|
+
|
288
|
+
# Moonshine for everything!
|
289
|
+
|
290
|
+
Even if this readme is heavily Rails-centered, remember that Moonshine can build
|
291
|
+
method chains to be run on any object. This is because the `subject` can be any
|
292
|
+
object you'd like.
|
293
|
+
|
294
|
+
Take a look at this quick example with a string:
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
class StringQuery < Moonshine::Base
|
298
|
+
subject -> { 'a dog' }
|
299
|
+
|
300
|
+
param :upper, call: :upcase, as_boolean: true
|
301
|
+
param :capitalize, as_boolean: true
|
302
|
+
param :append, call: :concat
|
303
|
+
param :concat, transform: :reverse
|
304
|
+
|
305
|
+
param :exclaims, default: true, as_boolean: true do |subject, value|
|
306
|
+
subject.concat(value || '!')
|
307
|
+
end
|
308
|
+
|
309
|
+
param :append_a_cat do |subject, value|
|
310
|
+
"#{subject} #{value} with a cat"
|
311
|
+
end
|
312
|
+
|
313
|
+
def self.reverse(value)
|
314
|
+
value.reverse
|
315
|
+
end
|
316
|
+
end
|
317
|
+
```
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
StringQuery.new({ upper: true }).run
|
321
|
+
=> "A DOG"
|
322
|
+
|
323
|
+
StringQuery.new({}).run
|
324
|
+
=> "a dog!"
|
325
|
+
|
326
|
+
StringQuery.new({ capitalize: true }).run
|
327
|
+
=> "A dog"
|
328
|
+
|
329
|
+
StringQuery.new({ append: ' go aroud' }).run
|
330
|
+
=> "a dog go aroud"
|
331
|
+
|
332
|
+
StringQuery.new({ concat: 'tac a dna ' }).run
|
333
|
+
=> "a dog and a cat"
|
334
|
+
```
|
335
|
+
|
22
336
|
|
23
337
|
## Contributing
|
24
338
|
|
data/lib/moonshine.rb
CHANGED
data/lib/moonshine/base.rb
CHANGED
@@ -1,36 +1,31 @@
|
|
1
1
|
module Moonshine
|
2
2
|
class Base
|
3
|
-
|
4
|
-
attr_accessor :
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@subject = subject || self.class.default_subject
|
10
|
-
@
|
3
|
+
attr_reader :chain
|
4
|
+
attr_accessor :params, :subject
|
5
|
+
|
6
|
+
include DSL
|
7
|
+
|
8
|
+
def initialize(params, subject = nil)
|
9
|
+
@subject = subject || self.class.default_subject.call
|
10
|
+
@params = params
|
11
|
+
@chain = []
|
12
|
+
if self.class.default_chain
|
13
|
+
self.class.default_chain.each do |params|
|
14
|
+
add_filter_to_chain(params[:name], params[:options], ¶ms[:block])
|
15
|
+
end
|
16
|
+
end
|
11
17
|
end
|
12
18
|
|
13
|
-
def
|
14
|
-
@chain <<
|
19
|
+
def add_filter_to_chain(name, **options, &block)
|
20
|
+
@chain << Moonshine::Filter.new(name, options, &block)
|
15
21
|
end
|
16
22
|
|
17
23
|
def run
|
18
|
-
chain.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
class << self
|
23
|
-
|
24
|
-
attr_accessor :default_subject, :default_chain
|
25
|
-
|
26
|
-
def subject(subject)
|
27
|
-
self.default_subject = subject
|
28
|
-
end
|
29
|
-
|
30
|
-
def param(name, call: nil, **options, &block)
|
31
|
-
self.default_chain ||= []
|
32
|
-
self.default_chain << Moonshine::Filter.new(name, method_name: (call || name), **options, &block)
|
24
|
+
chain.inject(subject) do |subject, filter|
|
25
|
+
filter.params = params
|
26
|
+
subject = filter.execute(subject)
|
33
27
|
end
|
34
28
|
end
|
35
29
|
end
|
36
30
|
end
|
31
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Moonshine
|
2
|
+
module DSL
|
3
|
+
def self.included(base)
|
4
|
+
base.send :extend, ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
attr_accessor :default_subject, :default_chain
|
9
|
+
|
10
|
+
def subject(subject)
|
11
|
+
@default_subject = subject
|
12
|
+
end
|
13
|
+
|
14
|
+
def param(name, **options, &block)
|
15
|
+
@default_chain ||= []
|
16
|
+
options[:transform_class] ||= self
|
17
|
+
@default_chain << { name: name, options: options, block: block }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
data/lib/moonshine/filter.rb
CHANGED
@@ -1,41 +1,44 @@
|
|
1
1
|
module Moonshine
|
2
2
|
class Filter
|
3
3
|
|
4
|
-
attr_accessor :name, :
|
4
|
+
attr_accessor :name, :params, :options
|
5
5
|
|
6
|
-
def initialize(name,
|
6
|
+
def initialize(name, **options, &block)
|
7
7
|
@name = name
|
8
|
-
|
8
|
+
options[:call] ||= block || name
|
9
9
|
@options = options
|
10
10
|
end
|
11
11
|
|
12
|
-
def execute(
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def method_call
|
21
|
-
if method_name.is_a? Proc
|
22
|
-
method_name.call(klass.subject, *args)
|
12
|
+
def execute(subject)
|
13
|
+
return subject unless to_execute?
|
14
|
+
if options[:as_boolean]
|
15
|
+
subject.send(options[:call])
|
23
16
|
else
|
24
|
-
|
17
|
+
if options[:call].is_a? Proc
|
18
|
+
options[:call].call(subject, args)
|
19
|
+
else
|
20
|
+
subject.send(options[:call], args)
|
21
|
+
end
|
25
22
|
end
|
26
23
|
end
|
27
24
|
|
25
|
+
def to_execute?
|
26
|
+
!!default
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
28
31
|
def args
|
29
|
-
|
32
|
+
transform(default)
|
30
33
|
end
|
31
34
|
|
32
|
-
def
|
33
|
-
|
35
|
+
def default
|
36
|
+
params[name] || options[:default]
|
34
37
|
end
|
35
38
|
|
36
|
-
def
|
37
|
-
return
|
38
|
-
|
39
|
+
def transform(params)
|
40
|
+
return params unless options[:transform]
|
41
|
+
options[:transform_class].send(options[:transform], params)
|
39
42
|
end
|
40
43
|
end
|
41
44
|
end
|
data/lib/moonshine/version.rb
CHANGED
data/moonshine.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Moonshine::VERSION
|
9
9
|
spec.authors = ["Alessio Rocco"]
|
10
10
|
spec.email = ["alessio.rocco.lt@gmail.com"]
|
11
|
-
spec.summary = %q{
|
12
|
-
spec.description = %q{
|
11
|
+
spec.summary = %q{A configuration driven method chain builder.}
|
12
|
+
spec.description = %q{Moonshine removes the complexity in building conditional method chains by providing a way to call a list of methods based on some input parameters.}
|
13
13
|
spec.homepage = "https://github.com/nebulab/moonshine"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -1,91 +1,64 @@
|
|
1
|
-
require 'test_helper'
|
1
|
+
require 'test_helper'
|
2
2
|
|
3
3
|
describe Moonshine::Base do
|
4
|
-
before(:each) do
|
5
|
-
@chain_builder = Class.new(Moonshine::Base)
|
6
|
-
@default_subject = mock('default_subject')
|
7
|
-
end
|
8
4
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
5
|
+
let(:tale) { Tale.new }
|
6
|
+
let(:tale_generator){
|
7
|
+
EmptyTaleGenerator.new({ beginning: true }, tale)
|
8
|
+
}
|
13
9
|
|
14
|
-
|
15
|
-
@chain_builder.default_subject.must_equal @default_subject
|
16
|
-
end
|
10
|
+
describe '#add_filter_to_chain' do
|
17
11
|
|
18
|
-
|
19
|
-
it 'sets subject as default subject' do
|
20
|
-
@chain_builder.new({}).subject.must_equal @default_subject
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe 'when it is initialized with a subject' do
|
25
|
-
let(:subject){ mock('subject') }
|
12
|
+
let(:params) { [:beginning] }
|
26
13
|
|
27
|
-
|
28
|
-
|
29
|
-
|
14
|
+
it 'instantiates a Moonshine::Filter' do
|
15
|
+
Moonshine::Filter.expects(:new).with(*params.push({}))
|
16
|
+
tale_generator.add_filter_to_chain(:beginning)
|
30
17
|
end
|
31
|
-
end
|
32
18
|
|
33
|
-
|
34
|
-
it 'instantiates a Moonshine::Filter
|
35
|
-
Moonshine::Filter.expects(:new).with(:
|
36
|
-
|
19
|
+
describe 'when params have options' do
|
20
|
+
it 'instantiates a Moonshine::Filter with options' do
|
21
|
+
Moonshine::Filter.expects(:new).with(*params.push({as_boolean: true}))
|
22
|
+
tale_generator.add_filter_to_chain(:beginning, as_boolean: true)
|
37
23
|
end
|
38
24
|
|
39
|
-
|
40
|
-
it 'instantiates a Moonshine::Filter class with call same as first param' do
|
41
|
-
Moonshine::Filter.expects(:new).with(:filter_name, method_name: :filter_name)
|
42
|
-
@chain_builder.param :filter_name
|
43
|
-
end
|
25
|
+
end
|
44
26
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
27
|
+
describe 'when params have a block' do
|
28
|
+
it 'instantiates a Moonshine::Filter with a block' do
|
29
|
+
a_block = Proc.new {}
|
30
|
+
Moonshine::Filter.expects(:new).with(*params.push({}, &a_block))
|
31
|
+
tale_generator.add_filter_to_chain(:beginning, &a_block)
|
51
32
|
end
|
52
33
|
end
|
53
34
|
|
54
|
-
it 'adds
|
55
|
-
filter =
|
35
|
+
it 'adds a Moonshine:Filter instance to filter_chain' do
|
36
|
+
filter = mock('a filter')
|
56
37
|
Moonshine::Filter.stubs(:new).returns(filter)
|
57
|
-
|
58
|
-
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
describe '#add' do
|
63
|
-
it 'adds filter to chain' do
|
64
|
-
filter = mock('filter')
|
65
|
-
chain_builder_instance = @chain_builder.new({})
|
66
|
-
chain_builder_instance.add(filter)
|
67
|
-
chain_builder_instance.chain.must_equal [filter]
|
38
|
+
tale_generator.add_filter_to_chain(:beginning)
|
39
|
+
tale_generator.chain.must_include(filter)
|
68
40
|
end
|
69
41
|
end
|
70
42
|
|
71
43
|
describe '#run' do
|
44
|
+
let(:tale_generator){
|
45
|
+
EmptyTaleGenerator.new({ beginning: true, conclusion: true }, tale)
|
46
|
+
}
|
72
47
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
filter2.expects(:execute).with(chain_builder_instance)
|
83
|
-
chain_builder_instance.run
|
48
|
+
before do
|
49
|
+
tale_generator.add_filter_to_chain(:beginning, as_boolean: true)
|
50
|
+
tale_generator.add_filter_to_chain(:conclusion, as_boolean: true)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'sends execute on each filter in chain' do
|
54
|
+
tale_generator.chain.first.expects(:execute)
|
55
|
+
tale_generator.chain.last.expects(:execute)
|
56
|
+
tale_generator.run
|
84
57
|
end
|
85
58
|
|
86
|
-
it 'returns
|
87
|
-
|
88
|
-
chain_builder_instance.run.must_equal chain_builder_instance.subject
|
59
|
+
it 'returns execute result of last filter in chain' do
|
60
|
+
tale_generator.run.must_be_kind_of Tale
|
89
61
|
end
|
90
62
|
end
|
91
63
|
end
|
64
|
+
|
@@ -1,91 +1,138 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
describe Moonshine::Filter do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
|
5
|
+
let(:tale) { Tale.new }
|
6
|
+
let(:filter) { Moonshine::Filter.new(:character) }
|
7
|
+
|
8
|
+
describe '#to_execute?' do
|
9
|
+
describe 'when params have a key like filter name' do
|
10
|
+
it 'returns true' do
|
11
|
+
filter.params = { character: 'a developer' }
|
12
|
+
filter.to_execute?.must_equal true
|
11
13
|
end
|
12
14
|
end
|
13
|
-
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
filter.execute(chain_builder_instance)
|
16
|
+
describe 'when options have a default value' do
|
17
|
+
it 'returns true' do
|
18
|
+
filter.options[:default] = true
|
19
|
+
filter.params = { antagonist: 'a designer'}
|
20
|
+
filter.to_execute?.must_equal true
|
21
|
+
end
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
describe 'when params does not have a key like filter name and does
|
25
|
+
not have a default value' do
|
26
|
+
it 'returns false' do
|
27
|
+
filter.params = { antagonist: 'a designer'}
|
28
|
+
filter.to_execute?.must_equal false
|
29
|
+
end
|
28
30
|
end
|
31
|
+
end
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
filter =
|
34
|
-
|
35
|
-
filter.method_name.expects(:call).with(chain_builder_instance.subject, 1)
|
36
|
-
filter.execute(chain_builder_instance)
|
33
|
+
describe '#execute' do
|
34
|
+
describe 'when to_execute? return false' do
|
35
|
+
it 'returns given subject' do
|
36
|
+
filter.params = { antagonist: 'a designer'}
|
37
|
+
filter.execute(tale).must_equal tale
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
|
-
describe '
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
chain_builder_instance.subject.stubs(:filter)
|
45
|
-
chain_builder_instance.expects(:transform_method).with(1)
|
46
|
-
filter.execute(chain_builder_instance)
|
47
|
-
end
|
41
|
+
describe 'when to_execute? return true' do
|
42
|
+
|
43
|
+
before do
|
44
|
+
filter.params = { character: 'a developer' }
|
48
45
|
end
|
49
46
|
|
50
|
-
describe '
|
51
|
-
it '
|
52
|
-
filter.options[:
|
53
|
-
|
54
|
-
|
55
|
-
filter.execute(chain_builder_instance)
|
47
|
+
describe 'and call is a block' do
|
48
|
+
it 'calls given block' do
|
49
|
+
filter.options[:call] = -> (subject, params){}
|
50
|
+
filter.options[:call].expects(:call).with(tale, 'a developer')
|
51
|
+
filter.execute(tale)
|
56
52
|
end
|
53
|
+
end
|
57
54
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
filter.execute(chain_builder_instance)
|
63
|
-
end
|
55
|
+
it 'sends method to given subject' do
|
56
|
+
tale.expects(:character).with('a developer')
|
57
|
+
filter.execute(tale)
|
58
|
+
end
|
64
59
|
|
65
|
-
|
66
|
-
|
67
|
-
chain_builder_instance.filters = {}
|
68
|
-
chain_builder_instance.subject.expects(:filter).never
|
69
|
-
filter.execute(chain_builder_instance)
|
70
|
-
end
|
60
|
+
it 'returns given subject' do
|
61
|
+
filter.execute(tale).must_equal tale
|
71
62
|
end
|
72
63
|
|
73
|
-
describe '
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
64
|
+
describe 'options' do
|
65
|
+
describe 'transform' do
|
66
|
+
before do
|
67
|
+
filter.options[:transform_class] = TaleTransform
|
68
|
+
filter.options[:transform] = :to_upper
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'sends transform method on given transform class' do
|
72
|
+
TaleTransform.expects(:to_upper).with('a developer')
|
73
|
+
filter.execute(tale)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'sends method to given subject with transformed value' do
|
77
|
+
tale.expects(:character).with('A DEVELOPER')
|
78
|
+
filter.execute(tale)
|
79
|
+
end
|
79
80
|
end
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
describe 'default' do
|
83
|
+
before do
|
84
|
+
filter.options[:default] = 'a designer'
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'when params have key like filter name' do
|
88
|
+
it 'sends method to given subject with given value' do
|
89
|
+
tale.expects(:character).with('a developer')
|
90
|
+
filter.execute(tale)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe 'when params does not have key like filter name' do
|
95
|
+
it 'sends method to given subject with default value' do
|
96
|
+
tale.expects(:character).with('a designer')
|
97
|
+
filter.params = {}
|
98
|
+
filter.execute(tale)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'as_boolean' do
|
104
|
+
before do
|
105
|
+
filter.options[:as_boolean] = true
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'when params have key like filter name' do
|
109
|
+
describe 'and value is true' do
|
110
|
+
it 'sends method to given subject whitout params' do
|
111
|
+
tale.expects(:character).with()
|
112
|
+
filter.params = { character: true }
|
113
|
+
filter.execute(tale)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe 'and value is false' do
|
118
|
+
it 'do not sends method to given subject' do
|
119
|
+
tale.expects(:character).with().never
|
120
|
+
filter.params = { character: false }
|
121
|
+
filter.execute(tale)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'when params does not have key like filter name' do
|
127
|
+
it 'do not sends method to given subject' do
|
128
|
+
tale.expects(:character).with().never
|
129
|
+
filter.params = {}
|
130
|
+
filter.execute(tale)
|
131
|
+
end
|
132
|
+
end
|
86
133
|
end
|
87
134
|
end
|
88
135
|
end
|
89
136
|
end
|
90
|
-
|
91
137
|
end
|
138
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe 'Given a class to generate a tale' do
|
4
|
+
|
5
|
+
let(:tale_generator){ TaleGenerator.new({}) }
|
6
|
+
|
7
|
+
describe 'when no params' do
|
8
|
+
it 'tell this story' do
|
9
|
+
tale_generator.run.story.must_equal "#{Tale::BEGINNING}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "when params are { ending: true }" do
|
14
|
+
it 'tell this story' do
|
15
|
+
tale_generator.params = { ending: true }
|
16
|
+
tale_generator.run.story.must_equal "#{Tale::BEGINNING} #{Tale::CONCLUSION}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "when params are { character: 'a developer' }" do
|
21
|
+
it 'tell this story' do
|
22
|
+
tale_generator.params = { character: 'a developer' }
|
23
|
+
tale_generator.run.story.must_equal "#{Tale::BEGINNING} a developer"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when params are { character: 'a developer' }" do
|
28
|
+
it 'tell this story' do
|
29
|
+
tale_generator.params = { character: 'a developer',
|
30
|
+
in_the_middle: 'that write a gem' }
|
31
|
+
tale_generator.run.story.must_equal(
|
32
|
+
"#{Tale::BEGINNING} a developer that write a gem"
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Tale
|
2
|
+
|
3
|
+
BEGINNING = 'long time ago ther was'
|
4
|
+
CONCLUSION = 'and they lived happily ever after'
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@story = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def story
|
11
|
+
story = []
|
12
|
+
story << @story[:beginning]
|
13
|
+
story << @story[:character]
|
14
|
+
story << @story[:before_conclusion]
|
15
|
+
story << @story[:conclusion]
|
16
|
+
story.delete(nil)
|
17
|
+
story.join(' ')
|
18
|
+
end
|
19
|
+
|
20
|
+
def character(name)
|
21
|
+
@story[:character] = name
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def beginning
|
26
|
+
@story[:beginning] = BEGINNING
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def before_conclusion(value)
|
31
|
+
@story[:before_conclusion] = value
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def conclusion
|
36
|
+
@story[:conclusion] = CONCLUSION
|
37
|
+
self
|
38
|
+
end
|
39
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -7,6 +7,10 @@ require 'moonshine'
|
|
7
7
|
require 'minitest/autorun'
|
8
8
|
require 'mocha/mini_test'
|
9
9
|
require 'minitest/pride'
|
10
|
+
require 'support/tale_transform'
|
11
|
+
require 'support/tale'
|
12
|
+
require 'support/tale_generator'
|
13
|
+
require 'support/empty_tale_generator'
|
10
14
|
|
11
15
|
class MiniTest::Spec
|
12
16
|
end
|
metadata
CHANGED
@@ -1,107 +1,114 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: moonshine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessio Rocco
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.5'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.5'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '5.2'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '5.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: mocha
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '1.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: coveralls
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - ~>
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0.7'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - ~>
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.7'
|
83
|
-
description:
|
83
|
+
description: Moonshine removes the complexity in building conditional method chains
|
84
|
+
by providing a way to call a list of methods based on some input parameters.
|
84
85
|
email:
|
85
86
|
- alessio.rocco.lt@gmail.com
|
86
87
|
executables: []
|
87
88
|
extensions: []
|
88
89
|
extra_rdoc_files: []
|
89
90
|
files:
|
90
|
-
- .gitignore
|
91
|
-
- .ruby-version
|
92
|
-
- .travis.yml
|
91
|
+
- ".gitignore"
|
92
|
+
- ".ruby-version"
|
93
|
+
- ".travis.yml"
|
93
94
|
- Gemfile
|
94
95
|
- LICENSE.txt
|
95
96
|
- README.md
|
96
97
|
- Rakefile
|
97
98
|
- lib/moonshine.rb
|
98
99
|
- lib/moonshine/base.rb
|
100
|
+
- lib/moonshine/dsl.rb
|
99
101
|
- lib/moonshine/filter.rb
|
100
102
|
- lib/moonshine/version.rb
|
101
103
|
- moonshine.gemspec
|
102
104
|
- test/lib/moonshine/base_test.rb
|
103
105
|
- test/lib/moonshine/filter_test.rb
|
104
106
|
- test/lib/moonshine/version_test.rb
|
107
|
+
- test/lib/moonshine_test.rb
|
108
|
+
- test/support/empty_tale_generator.rb
|
109
|
+
- test/support/tale.rb
|
110
|
+
- test/support/tale_generator.rb
|
111
|
+
- test/support/tale_transform.rb
|
105
112
|
- test/test_helper.rb
|
106
113
|
homepage: https://github.com/nebulab/moonshine
|
107
114
|
licenses:
|
@@ -113,22 +120,28 @@ require_paths:
|
|
113
120
|
- lib
|
114
121
|
required_ruby_version: !ruby/object:Gem::Requirement
|
115
122
|
requirements:
|
116
|
-
- -
|
123
|
+
- - ">="
|
117
124
|
- !ruby/object:Gem::Version
|
118
125
|
version: '2.0'
|
119
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
127
|
requirements:
|
121
|
-
- -
|
128
|
+
- - ">="
|
122
129
|
- !ruby/object:Gem::Version
|
123
|
-
version:
|
130
|
+
version: '0'
|
124
131
|
requirements: []
|
125
132
|
rubyforge_project:
|
126
|
-
rubygems_version: 2.0
|
133
|
+
rubygems_version: 2.3.0
|
127
134
|
signing_key:
|
128
135
|
specification_version: 4
|
129
|
-
summary:
|
136
|
+
summary: A configuration driven method chain builder.
|
130
137
|
test_files:
|
131
138
|
- test/lib/moonshine/base_test.rb
|
132
139
|
- test/lib/moonshine/filter_test.rb
|
133
140
|
- test/lib/moonshine/version_test.rb
|
141
|
+
- test/lib/moonshine_test.rb
|
142
|
+
- test/support/empty_tale_generator.rb
|
143
|
+
- test/support/tale.rb
|
144
|
+
- test/support/tale_generator.rb
|
145
|
+
- test/support/tale_transform.rb
|
134
146
|
- test/test_helper.rb
|
147
|
+
has_rdoc:
|