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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aae2eb56018f3507bd3d5f36ded77930c1b1c7e6
4
- data.tar.gz: b240d7956adb487b96eee495552fe492ade864c4
3
+ metadata.gz: e1cbffb31dd0dcced747bb06c3cc6a6370599e6e
4
+ data.tar.gz: e8a3183d13be8f733e2d85fbd12cb95b336d3c8f
5
5
  SHA512:
6
- metadata.gz: 47b2be0dd1bf28acf80a0bb3224e383398aaf6250e1047685089b9e41bef8a5950d9c9745ae37aeb3375e1bb51d350a8f27f31793c74851ef8daddd9f333c26e
7
- data.tar.gz: 4867578bd37b75012ff1c23ca5e3f7da42b8d5db571dc511dda9fca2e5e382ba3ce4a5c9d831cae744a93adfd6771d635ebc9d81c1533156059c1753b107905f
6
+ metadata.gz: dc85d07677d1a4a4e634086e3c471e62552eb22e73c0ff063709b16aaf62d38ae915fb3b2c8198b95852f0e0d64c2d4273f90f1fedef4c781185128b751ff629
7
+ data.tar.gz: 14ab17f3c26783a930ffdd66dca33fbcfe0685bb03d2fd60acab3b8e602c7c622420ad182a02080717def56c05072ce20632f0b10f74b0936d7601a0390a81f1
data/README.md CHANGED
@@ -1,24 +1,338 @@
1
- # Moonshine [![Gem Version](https://badge.fury.io/rb/moonshine.png)](http://badge.fury.io/rb/moonshine) [![Build Status](https://travis-ci.org/nebulab/moonshine.png?branch=master)](https://travis-ci.org/nebulab/moonshine) [![Coverage Status](https://coveralls.io/repos/nebulab/moonshine/badge.png?branch=master)](https://coveralls.io/r/nebulab/moonshine?branch=master) [![Code Climate](https://codeclimate.com/github/nebulab/moonshine.png)](https://codeclimate.com/github/nebulab/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
- TODO: Write a gem description
7
+ Moonshine is a configuration driven method chain builder.
4
8
 
5
- ## Installation
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
- Add this line to your application's Gemfile:
52
+ ```
53
+ GET http://my.awesome-blog.com/posts?category=cats&with_tags=cute,lovely
54
+ ```
8
55
 
9
- gem 'moonshine'
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
- And then execute:
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
- $ bundle
71
+ ```ruby
72
+ $ bundle
73
+ ```
14
74
 
15
- Or install it yourself as:
75
+ Or you can install it yourself by running:
16
76
 
17
- $ gem install moonshine
77
+ ```ruby
78
+ $ gem install moonshine
79
+ ```
18
80
 
19
81
  ## Usage
20
82
 
21
- TODO: Write usage instructions here
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
 
@@ -1,7 +1,7 @@
1
1
  require "moonshine/version"
2
- require "moonshine/base"
2
+ require "moonshine/dsl"
3
3
  require "moonshine/filter"
4
+ require "moonshine/base"
4
5
 
5
6
  module Moonshine
6
- # Your code goes here...
7
7
  end
@@ -1,36 +1,31 @@
1
1
  module Moonshine
2
2
  class Base
3
-
4
- attr_accessor :filters
5
- attr_reader :chain, :subject
6
-
7
- def initialize(filters, subject = nil)
8
- @filters = filters
9
- @subject = subject || self.class.default_subject
10
- @chain = self.class.default_chain || []
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], &params[:block])
15
+ end
16
+ end
11
17
  end
12
18
 
13
- def add(filter)
14
- @chain << filter
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.each { |filter| @subject = filter.execute(self) }
19
- @subject
20
- end
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
+
@@ -1,41 +1,44 @@
1
1
  module Moonshine
2
2
  class Filter
3
3
 
4
- attr_accessor :name, :method_name, :options, :klass
4
+ attr_accessor :name, :params, :options
5
5
 
6
- def initialize(name, method_name: nil, **options, &block)
6
+ def initialize(name, **options, &block)
7
7
  @name = name
8
- @method_name = block || method_name
8
+ options[:call] ||= block || name
9
9
  @options = options
10
10
  end
11
11
 
12
- def execute(klass)
13
- @klass = klass
14
- return method_call if klass.filters[name] || options[:default]
15
- klass.subject
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
- klass.subject.send(method_name, *args)
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
- set_transform(set_default(klass.filters[name])) unless options[:as_boolean]
32
+ transform(default)
30
33
  end
31
34
 
32
- def set_default value
33
- value || options[:default]
35
+ def default
36
+ params[name] || options[:default]
34
37
  end
35
38
 
36
- def set_transform value
37
- return klass.send(options[:transform], value) if options[:transform]
38
- value
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
@@ -1,3 +1,3 @@
1
1
  module Moonshine
2
- VERSION = "0.2.1.pre"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -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{Conditional method chaining}
12
- spec.description = %q{Conditional method chaining}
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
- describe '.subject' do
10
- before do
11
- @chain_builder.default_subject = @default_subject
12
- end
5
+ let(:tale) { Tale.new }
6
+ let(:tale_generator){
7
+ EmptyTaleGenerator.new({ beginning: true }, tale)
8
+ }
13
9
 
14
- it 'sets default subject class attribute' do
15
- @chain_builder.default_subject.must_equal @default_subject
16
- end
10
+ describe '#add_filter_to_chain' do
17
11
 
18
- describe 'when it is initialized without a subject' do
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
- it 'sets subject as requested' do
28
- @chain_builder.new({}, subject).subject.must_equal subject
29
- end
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
- describe '.param' do
34
- it 'instantiates a Moonshine::Filter class' do
35
- Moonshine::Filter.expects(:new).with(:filter_name, method_name: :method)
36
- @chain_builder.param :filter_name, call: :method
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
- describe 'when :call is not given' do
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
- describe 'and a block is given' do
46
- it 'instantiates a Moonshine::Filter call with block as call' do
47
- block = Proc.new{ |subject| subject }
48
- Moonshine::Filter.expects(:new).with(:filter_name, &block)
49
- @chain_builder.param :filter_name, &block
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 filter to default_chain' do
55
- filter = mock('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
- @chain_builder.param :filter_name, call: :scope
58
- @chain_builder.default_chain.must_equal [filter]
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
- it 'run execute on each filter' do
74
- filter1 = mock('filter1')
75
- filter1.stubs(:name).returns(:filter1)
76
- filter2 = mock('filter2')
77
- filter2.stubs(:name).returns(:filter2)
78
- filters = [filter1, filter2]
79
- @chain_builder.default_chain = filters
80
- chain_builder_instance = @chain_builder.new({ filter1: 1, filter2: 2})
81
- filter1.expects(:execute).with(chain_builder_instance)
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 subject' do
87
- chain_builder_instance = @chain_builder.new({})
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
- before(:each) do
5
- default_subject = mock('default_subject')
6
- @chain_builder = Class.new(Moonshine::Base) do
7
- subject default_subject
8
- param :name, call: :scope
9
- param :block do |subject, value|
10
- subject.some_method(value)
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
- describe '#execute' do
16
- let(:filter) { Moonshine::Filter.new(:filter, method_name: :filter) }
17
- let(:chain_builder_instance) { @chain_builder.new({ filter: 1 }) }
18
-
19
- it 'sends scope to klass' do
20
- chain_builder_instance.subject.expects(:filter).with(1)
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
- it 'return subject when default and value are nil' do
25
- filter.options[:default] = nil
26
- chain_builder_instance.filters = {}
27
- filter.execute(chain_builder_instance).must_equal chain_builder_instance.subject
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
- describe 'when block is given' do
31
- it 'calls block' do
32
- block = Proc.new { |subject, value| subject.some_method(value) }
33
- filter = Moonshine::Filter.new(:filter, &block)
34
- chain_builder_instance = @chain_builder.new({ filter: 1 })
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 'options' do
41
- describe 'transform' do
42
- it 'changes value with transform method from klass' do
43
- filter.options[:transform] = :transform_method
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 'default' do
51
- it 'uses default value if filter is nil' do
52
- filter.options[:default] = 2
53
- chain_builder_instance.filters = {}
54
- chain_builder_instance.subject.expects(:filter).with(2)
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
- it 'not use default value if filter is not nil' do
59
- filter.options[:default] = 2
60
- chain_builder_instance.filters = { filter: 1 }
61
- chain_builder_instance.subject.expects(:filter).with(1)
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
- it 'not sends filter if default and value are nil' do
66
- filter.options[:default] = nil
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 'as_boolean' do
74
- it 'sends method without value when true' do
75
- filter.options[:as_boolean] = true
76
- chain_builder_instance.filters = { filter: true }
77
- chain_builder_instance.subject.expects(:filter)
78
- filter.execute(chain_builder_instance)
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
- it 'sends method without value when false' do
82
- filter.options[:as_boolean] = true
83
- chain_builder_instance.filters = { filter: false }
84
- chain_builder_instance.subject.expects(:filter).never
85
- filter.execute(chain_builder_instance)
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
+
@@ -2,7 +2,7 @@ require 'test_helper'
2
2
 
3
3
  describe Moonshine::VERSION do
4
4
 
5
- it 'is 0.2.1.pre' do
6
- Moonshine::VERSION.must_equal '0.2.1.pre'
5
+ it 'is 0.3.0' do
6
+ Moonshine::VERSION.must_equal '0.3.0'
7
7
  end
8
8
  end
@@ -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,2 @@
1
+ class EmptyTaleGenerator < Moonshine::Base
2
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ class TaleGenerator < Moonshine::Base
2
+ subject -> { Tale.new }
3
+ param :beginning, default: true, as_boolean: true
4
+ param :character
5
+ param :in_the_middle, call: :before_conclusion
6
+ param :ending, call: :conclusion, as_boolean: true
7
+ end
@@ -0,0 +1,6 @@
1
+ class TaleTransform
2
+
3
+ def self.to_upper(value)
4
+ value.upcase
5
+ end
6
+ end
@@ -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.2.1.pre
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-02-02 00:00:00.000000000 Z
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: Conditional method chaining
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: 1.3.1
130
+ version: '0'
124
131
  requirements: []
125
132
  rubyforge_project:
126
- rubygems_version: 2.0.14
133
+ rubygems_version: 2.3.0
127
134
  signing_key:
128
135
  specification_version: 4
129
- summary: Conditional method chaining
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: