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 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: