moonshine 0.2.1.pre → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![GemVersion](https://badge.fury.io/rb/moonshine.png)](http://badge.fury.io/rb/moonshine)
|
3
|
+
[![BuildStatus](https://travis-ci.org/nebulab/moonshine.png?branch=master)](https://travis-ci.org/nebulab/moonshine)
|
4
|
+
[![CoverageStatus](https://coveralls.io/repos/nebulab/moonshine/badge.png?branch=master)](https://coveralls.io/r/nebulab/moonshine?branch=master)
|
5
|
+
[![CodeClimate](https://codeclimate.com/github/nebulab/moonshine.png)](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:
|