disposable 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/README.md +44 -128
- data/database.sqlite3 +0 -0
- data/disposable.gemspec +2 -1
- data/gemfiles/Gemfile.rails-2.3 +1 -0
- data/gemfiles/Gemfile.rails-2.3.lock +8 -7
- data/gemfiles/Gemfile.rails-3.0.lock +7 -7
- data/lib/disposable/twin/finders.rb +29 -0
- data/lib/disposable/twin.rb +55 -20
- data/lib/disposable/version.rb +1 -1
- data/lib/disposable.rb +3 -5
- data/test/composition_test.rb +1 -1
- data/test/test_helper.rb +5 -0
- data/test/twin/active_record_test.rb +65 -2
- data/test/twin/twin_test.rb +2 -2
- metadata +38 -30
- data/lib/disposable/facade/active_record.rb +0 -8
- data/lib/disposable/facade.rb +0 -94
- data/test/active_record_test.rb +0 -80
- data/test/facade_test.rb +0 -128
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90e97c08c2c16f6c4990b4e07aa18181665590f5
|
4
|
+
data.tar.gz: cadf5067b7b35c87ec40f2c1f33193d957a9b5ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8183c7256adf882d8506c53301bb9e9e60f8f0fb6e1d6f55c5da6fdd241667034c120a7209f1042126c241986935c29b9037775f29c4f1d50aa447e75ad4a513
|
7
|
+
data.tar.gz: 2222286b902c93dd4948b738ec44eeab397dab55bbb79cf043660d2ecb04eee7a96f4c5b0c0e5c27c3c367936d3e93b453dce5653af17f4a68ecab423189cf09
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,30 @@
|
|
1
1
|
# Disposable
|
2
2
|
|
3
|
+
_Provides domain objects on top of your ORM layer._
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
## Introduction
|
6
|
+
|
7
|
+
Disposable gives you "_Twins_" which are domain objects, decoupled from ActiveRecord, DataMapper or whatever ORM you use.
|
8
|
+
|
9
|
+
Twins are non-persistent domain objects. That is reflected in the name of the gem. However, they can read and write values from a persistent object.
|
10
|
+
|
11
|
+
Twins are an integral part of the [Trailblazer](https://github.com/apotonick/trailblazer) architectural style which provides clean layering of concerns.
|
12
|
+
|
13
|
+
They give you an encapsulated alternative to delegators that many projects use to separate domain and persistence and help you restricting the domain API.
|
14
|
+
|
15
|
+
## Why?
|
16
|
+
|
17
|
+
The goal is to have business logic sitting in twin classes, while your models (ideally) contain persistence configuration, only.
|
18
|
+
|
19
|
+
Beyond that, twins can be used in form objects, cells view models, representers, contracts, and actually any Ruby code :)
|
20
|
+
|
21
|
+
Twins may contain validations, nevertheless, in Trailblazer, validations (or "Contracts") sit one layer above. They still can be part of your domain, though.
|
7
22
|
|
8
23
|
## Twin
|
9
24
|
|
10
|
-
Twins implement light-weight domain objects that contain business logic - no persistance logic. They have read- and write access to a
|
25
|
+
Twins implement light-weight domain objects that contain business logic - no persistance logic. They have read- and write access to a persistent object (or a composition of those) and expose a sub-set of accessors to that persistent "brother", making it a "data mapper-like phantom".
|
11
26
|
|
12
|
-
Being designed to wrap
|
27
|
+
Being designed to wrap persistent objects, a typical "brother" class could be an ActiveRecord one.
|
13
28
|
|
14
29
|
```ruby
|
15
30
|
class Song < ActiveRecord::Base
|
@@ -17,13 +32,13 @@ class Song < ActiveRecord::Base
|
|
17
32
|
end
|
18
33
|
```
|
19
34
|
|
20
|
-
You don't wanna work with that
|
35
|
+
You don't wanna work with that persistent brother directly anymore. A twin will wrap it and indirect domain from persistance.
|
21
36
|
|
22
37
|
The twin itself has a _ridiculous_ simple API.
|
23
38
|
|
24
39
|
```ruby
|
25
40
|
class Twin::Song < Disposable::Twin
|
26
|
-
model ::Song # the
|
41
|
+
model ::Song # the persistent ActiveRecord brother class
|
27
42
|
|
28
43
|
property :title
|
29
44
|
property :album, twin: Album # a nested twin.
|
@@ -32,7 +47,7 @@ class Twin::Song < Disposable::Twin
|
|
32
47
|
|
33
48
|
### Creation
|
34
49
|
|
35
|
-
You can create fresh, blank-slate twins yourself. They will create a new
|
50
|
+
You can create fresh, blank-slate twins yourself. They will create a new persistent object automatically.
|
36
51
|
|
37
52
|
```ruby
|
38
53
|
song = Twin::Song.new(title: "Justified Black Eye")
|
@@ -43,10 +58,11 @@ song = Twin::Song.new(title: "Justified Black Eye")
|
|
43
58
|
This doesn't involve any database operations at all.
|
44
59
|
|
45
60
|
|
46
|
-
###
|
61
|
+
### Finders
|
62
|
+
|
63
|
+
You can use any finder/scope defined in your model to create twins.
|
47
64
|
|
48
|
-
|
49
|
-
Every `Twin` subclass exposes all finders from the brother class. However, instances from the persistance layer are automatically twin'ed.
|
65
|
+
Since `::find` is pretty common, it is defined directly on the twin class.
|
50
66
|
|
51
67
|
```ruby
|
52
68
|
song = Twin::Song.find(1)
|
@@ -54,6 +70,18 @@ song = Twin::Song.find(1)
|
|
54
70
|
#=> #<Twin::Song title: "Already Won" album: #<Twin::Album>>
|
55
71
|
```
|
56
72
|
|
73
|
+
This invokes the actual finder method on the model class. Every found model will simply be wrapped in its twin.
|
74
|
+
|
75
|
+
|
76
|
+
Any other scope or finder can be called on `finders`.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
song = Twin::Song.finders.where(name: "3 O'Clock Shot")
|
80
|
+
|
81
|
+
#=> [#<Twin::Song title: "3 O'Clock Shot" album: #<Twin::Album>>]
|
82
|
+
```
|
83
|
+
|
84
|
+
|
57
85
|
|
58
86
|
### Read And Write Access
|
59
87
|
|
@@ -65,7 +93,7 @@ song.title #=> "Already Won"
|
|
65
93
|
song.album.name #=> "The Songs of Tony Sly: A Tribute"
|
66
94
|
```
|
67
95
|
|
68
|
-
Note that writing to the twin **does not** change any state on the
|
96
|
+
Note that writing to the twin **does not** change any state on the persistent brother.
|
69
97
|
|
70
98
|
```ruby
|
71
99
|
song.title = "Still Winning" # no change on Song, no write to DB.
|
@@ -74,125 +102,13 @@ song.title = "Still Winning" # no change on Song, no write to DB.
|
|
74
102
|
|
75
103
|
### Saving
|
76
104
|
|
77
|
-
Calling `#save` will sync all properties to the brother object and advice the
|
105
|
+
Calling `#save` will sync all properties to the brother object and advice the persistent brother to save. This works recursively, meaning that nested twins will do the same with their pendant.
|
78
106
|
|
79
107
|
```ruby
|
80
108
|
song.save # write to DB.
|
81
109
|
```
|
82
110
|
|
83
|
-
|
84
|
-
* Lazy-loading
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
## Composition
|
89
|
-
|
90
|
-
hiding composed datastructures
|
91
|
-
mapping methods, with optional as:
|
92
|
-
|
93
|
-
|
94
|
-
## To be documented properly
|
95
|
-
|
96
|
-
Facade existing (model) class
|
97
|
-
where to decorate old instances from collections?
|
98
|
-
option.invoice_template => .items
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
class Invoice
|
103
|
-
class Option
|
104
|
-
facades InvoiceOption
|
105
|
-
|
106
|
-
collection :items, original => :invoice_template
|
107
|
-
def items invoice_template.collect ..
|
108
|
-
- or: opt.invoice_tempate.facade.
|
109
|
-
|
110
|
-
end
|
111
|
-
|
112
|
-
|
113
|
-
Why facades?
|
114
|
-
you don't wanna add code to existing shit
|
115
|
-
transparently change the API
|
116
|
-
you don't have to worry about what's happening in the underlying pile of shit (GstCalculations module), you only correct the API on top of it
|
117
|
-
no inheritance, composition whereever possible (interfaces between layers)
|
118
|
-
optional, still use old assets
|
119
|
-
don't/step-wise change existing "running" code
|
120
|
-
|
121
|
-
you basically don't change existing code but build extracted components on top of your legacy app
|
122
|
-
|
123
|
-
* facades
|
124
|
-
* overriding public methods in facade
|
125
|
-
* temporary Refinements
|
111
|
+
## Notes
|
126
112
|
|
127
|
-
*
|
128
|
-
|
129
|
-
|
130
|
-
"partial refactoring"
|
131
|
-
"explicit refactoring"
|
132
|
-
|
133
|
-
|
134
|
-
* make it simple to split existing model into smaller (reducing validations etc)
|
135
|
-
* mark dead code
|
136
|
-
* by explicit use of `facade` you can track "dirt"
|
137
|
-
|
138
|
-
* rename options => DSL
|
139
|
-
|
140
|
-
TODO: Write a gem description
|
141
|
-
* generator for Facades
|
142
|
-
loading of facades?
|
143
|
-
location of facades? i wanna have Facade::Client, not facades/ClientFacade.
|
144
|
-
FACADE keeps all configuration in one place (like a new has_many), also you can track which methods you actually need in your data model. this wouldn't be possible that easy with inheritance.
|
145
|
-
|
146
|
-
Facadable#facade
|
147
|
-
Facade#facaded
|
148
|
-
|
149
|
-
|
150
|
-
idea: collect # REFAC lines
|
151
|
-
|
152
|
-
|
153
|
-
running with Rails 2.3->4.0, 1.8.7 ..as that makes sense
|
154
|
-
|
155
|
-
|
156
|
-
## Refinement
|
157
|
-
|
158
|
-
injected into instance _after_ construction
|
159
|
-
|
160
|
-
## Build
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
FacadeClass
|
165
|
-
extend Build
|
166
|
-
|
167
|
-
module ClassMethods
|
168
|
-
def initializer
|
169
|
-
def another_class_method # FIXME: does that work?
|
170
|
-
|
171
|
-
make anonymous class, allow overriding initializer and (works?) add class methods.
|
172
|
-
|
173
|
-
|
174
|
-
## Installation
|
175
|
-
|
176
|
-
Add this line to your application's Gemfile:
|
177
|
-
|
178
|
-
gem 'disposable'
|
179
|
-
|
180
|
-
And then execute:
|
181
|
-
|
182
|
-
$ bundle
|
183
|
-
|
184
|
-
Or install it yourself as:
|
185
|
-
|
186
|
-
$ gem install disposable
|
187
|
-
|
188
|
-
## Usage
|
189
|
-
|
190
|
-
TODO: Write usage instructions here
|
191
|
-
|
192
|
-
## Contributing
|
193
|
-
|
194
|
-
1. Fork it
|
195
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
196
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
197
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
198
|
-
5. Create new Pull Request
|
113
|
+
* Twins don't know anything about the underlying persistance layer.
|
114
|
+
* Lazy-loading TBI
|
data/database.sqlite3
CHANGED
Binary file
|
data/disposable.gemspec
CHANGED
@@ -19,11 +19,12 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "uber"
|
22
|
-
spec.add_dependency "representable", "~>
|
22
|
+
spec.add_dependency "representable", "~> 2.0.0"
|
23
23
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
25
25
|
spec.add_development_dependency "rake"
|
26
26
|
spec.add_development_dependency "minitest"
|
27
27
|
spec.add_development_dependency "activerecord"
|
28
28
|
spec.add_development_dependency "sqlite3"
|
29
|
+
spec.add_development_dependency "database_cleaner"
|
29
30
|
end
|
data/gemfiles/Gemfile.rails-2.3
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
disposable (0.0.
|
4
|
+
disposable (0.0.4)
|
5
5
|
representable (~> 1.8.1)
|
6
6
|
uber
|
7
7
|
|
@@ -18,11 +18,10 @@ GEM
|
|
18
18
|
activeresource (2.3.18)
|
19
19
|
activesupport (= 2.3.18)
|
20
20
|
activesupport (2.3.18)
|
21
|
-
|
21
|
+
database_cleaner (1.3.0)
|
22
22
|
minitest (5.0.8)
|
23
|
-
multi_json (1.
|
24
|
-
nokogiri (1.
|
25
|
-
mini_portile (~> 0.5.0)
|
23
|
+
multi_json (1.10.0)
|
24
|
+
nokogiri (1.5.11)
|
26
25
|
rack (1.1.6)
|
27
26
|
rails (2.3.18)
|
28
27
|
actionmailer (= 2.3.18)
|
@@ -32,12 +31,12 @@ GEM
|
|
32
31
|
activesupport (= 2.3.18)
|
33
32
|
rake (>= 0.8.3)
|
34
33
|
rake (10.1.0)
|
35
|
-
representable (1.8.
|
34
|
+
representable (1.8.2)
|
36
35
|
multi_json
|
37
36
|
nokogiri
|
38
37
|
uber
|
39
38
|
sqlite3 (1.3.8)
|
40
|
-
uber (0.0.
|
39
|
+
uber (0.0.6)
|
41
40
|
|
42
41
|
PLATFORMS
|
43
42
|
ruby
|
@@ -45,8 +44,10 @@ PLATFORMS
|
|
45
44
|
DEPENDENCIES
|
46
45
|
activerecord
|
47
46
|
bundler (~> 1.3)
|
47
|
+
database_cleaner
|
48
48
|
disposable!
|
49
49
|
minitest
|
50
|
+
nokogiri (~> 1.5.0)
|
50
51
|
rails (= 2.3.18)
|
51
52
|
rake
|
52
53
|
sqlite3
|
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
disposable (0.0.
|
4
|
+
disposable (0.0.4)
|
5
5
|
representable (~> 1.8.1)
|
6
6
|
uber
|
7
7
|
|
@@ -35,11 +35,11 @@ GEM
|
|
35
35
|
abstract (>= 1.0.0)
|
36
36
|
i18n (0.5.0)
|
37
37
|
json (1.8.0)
|
38
|
-
mini_portile (0.
|
38
|
+
mini_portile (0.6.0)
|
39
39
|
minitest (5.0.8)
|
40
|
-
multi_json (1.
|
41
|
-
nokogiri (1.6.1)
|
42
|
-
mini_portile (
|
40
|
+
multi_json (1.10.0)
|
41
|
+
nokogiri (1.6.2.1)
|
42
|
+
mini_portile (= 0.6.0)
|
43
43
|
rack (1.2.8)
|
44
44
|
rack-mount (0.6.14)
|
45
45
|
rack (>= 1.0.0)
|
@@ -54,14 +54,14 @@ GEM
|
|
54
54
|
rake (10.1.0)
|
55
55
|
rdoc (3.12.2)
|
56
56
|
json (~> 1.4)
|
57
|
-
representable (1.8.
|
57
|
+
representable (1.8.2)
|
58
58
|
multi_json
|
59
59
|
nokogiri
|
60
60
|
uber
|
61
61
|
sqlite3 (1.3.8)
|
62
62
|
thor (0.14.6)
|
63
63
|
tzinfo (0.3.38)
|
64
|
-
uber (0.0.
|
64
|
+
uber (0.0.6)
|
65
65
|
|
66
66
|
PLATFORMS
|
67
67
|
ruby
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Disposable::Twin::Finders
|
2
|
+
def find(*args)
|
3
|
+
finders.find(*args)
|
4
|
+
end
|
5
|
+
|
6
|
+
# Use Song::Twin.finders.where(..) or whatever finder/scope is defined in the model.
|
7
|
+
# It will return each model wrapped in a Twin.
|
8
|
+
def finders
|
9
|
+
FinderBuilder.new(self, _model)
|
10
|
+
end
|
11
|
+
|
12
|
+
class FinderBuilder
|
13
|
+
def initialize(*args)
|
14
|
+
@twin_class, @model_class = *args
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def method_missing(*args, &block)
|
19
|
+
models = execute(*args, &block)
|
20
|
+
|
21
|
+
return @twin_class.new(models) unless models.respond_to?(:each) # sorry for all the magic, but that's how ActiveRecord works.
|
22
|
+
models.collect { |mdl| @twin_class.new(mdl) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute(*args, &block)
|
26
|
+
@model_class.send(*args, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/disposable/twin.rb
CHANGED
@@ -4,6 +4,13 @@ require 'representable/hash'
|
|
4
4
|
|
5
5
|
module Disposable
|
6
6
|
class Twin
|
7
|
+
class Definition < Representable::Definition
|
8
|
+
def dynamic_options
|
9
|
+
super + [:twin]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
7
14
|
class Decorator < Representable::Decorator
|
8
15
|
include Representable::Hash
|
9
16
|
include AllowSymbols
|
@@ -13,6 +20,10 @@ module Disposable
|
|
13
20
|
Class.new(self) # By subclassing, representable_attrs.clone is called.
|
14
21
|
end
|
15
22
|
|
23
|
+
def self.definition_class
|
24
|
+
Definition
|
25
|
+
end
|
26
|
+
|
16
27
|
def twin_names
|
17
28
|
representable_attrs.
|
18
29
|
find_all { |attr| attr[:twin] }.
|
@@ -44,19 +55,37 @@ module Disposable
|
|
44
55
|
property(name, options.merge(:collection => true), &block)
|
45
56
|
end
|
46
57
|
|
58
|
+
# this method should only be called in finders, and considered semi-private. it should only be called once as the top stack entry.
|
47
59
|
def self.from(model) # TODO: private.
|
48
60
|
new(model)
|
49
61
|
end
|
50
62
|
|
51
|
-
def self.new(model=
|
52
|
-
model,
|
53
|
-
super(model || _model.new, *[options].compact) # TODO: make this nicer.
|
63
|
+
def self.new(model={}, object_map=ObjectMap.new)
|
64
|
+
super(model, object_map)
|
54
65
|
end
|
55
66
|
|
56
|
-
|
57
|
-
|
67
|
+
|
68
|
+
# TODO: improve speed when setting up a twin.
|
69
|
+
def initialize(model, object_map)
|
70
|
+
options = {}
|
71
|
+
options, model = model, self.class._model.new if model.is_a?(Hash)
|
72
|
+
|
73
|
+
|
74
|
+
# model, options = nil, model if model.is_a?(Hash) # sorry but i wanna have the same API as ActiveRecord here.
|
75
|
+
@model = model #|| self.class._model.new
|
76
|
+
|
77
|
+
object_map[@model] = self # DISCUSS: how to we handle compositions here?
|
78
|
+
|
79
|
+
from_hash(
|
80
|
+
self.class.new_representer.new(@model).to_hash(:object_map => object_map). # always read from model, even when it's new.
|
81
|
+
merge(options)
|
82
|
+
)
|
58
83
|
end
|
59
84
|
|
85
|
+
|
86
|
+
require 'disposable/twin/finders'
|
87
|
+
extend Finders
|
88
|
+
|
60
89
|
# hash for #update_attributes (model API): {title: "Future World", album: <Album>}
|
61
90
|
def self.save_representer
|
62
91
|
# TODO: do that only at compile-time!
|
@@ -78,16 +107,22 @@ module Disposable
|
|
78
107
|
def self.new_representer
|
79
108
|
representer = Class.new(representer_class) # inherit configuration
|
80
109
|
|
81
|
-
# wrap incoming nested model in
|
110
|
+
# wrap incoming nested model in its Twin.
|
82
111
|
representer.representable_attrs.
|
83
112
|
find_all { |attr| attr[:twin] }.
|
84
113
|
each { |attr| attr.merge!(
|
85
|
-
:prepare => lambda { |object, args|
|
114
|
+
:prepare => lambda { |object, args|
|
115
|
+
if twin = args.user_options[:object_map][object]
|
116
|
+
twin
|
117
|
+
else
|
118
|
+
args.binding[:twin].evaluate(nil).new(object, args.user_options[:object_map])
|
119
|
+
end
|
120
|
+
}) }
|
86
121
|
|
87
122
|
# song_title => model.title
|
88
123
|
representer.representable_attrs.each do |attr|
|
89
124
|
attr.merge!(
|
90
|
-
:getter
|
125
|
+
:getter => lambda { |args| send("#{args.binding[:private_name]}") },
|
91
126
|
)
|
92
127
|
end
|
93
128
|
|
@@ -105,26 +140,23 @@ module Disposable
|
|
105
140
|
representer.representable_attrs.
|
106
141
|
each { |attr| attr.merge!(
|
107
142
|
:representable => true,
|
108
|
-
:serialize => lambda
|
143
|
+
:serialize => lambda do |twin, args|
|
144
|
+
processed = args.user_options[:processed_map]
|
145
|
+
|
146
|
+
twin.save(processed) unless processed[twin] # don't call save if it is already scheduled.
|
147
|
+
end
|
109
148
|
)}
|
110
149
|
|
111
150
|
representer
|
112
151
|
end
|
113
152
|
|
114
153
|
|
115
|
-
# TODO: improve speed when setting up a twin.
|
116
|
-
def initialize(model, options={})
|
117
|
-
@model = model
|
118
|
-
|
119
|
-
# DISCUSS: does the case exist where we get model AND options? if yes, test. if no, we can save the mapping and just use options.
|
120
|
-
from_hash(self.class.new_representer.new(model).to_hash.
|
121
|
-
merge(options))
|
122
|
-
end
|
123
|
-
|
124
154
|
# it's important to stress that #save is the only entry point where we hit the database after initialize.
|
125
|
-
def save # use that in Reform::AR.
|
155
|
+
def save(processed_map=ObjectMap.new) # use that in Reform::AR.
|
156
|
+
processed_map[self] = true
|
157
|
+
|
126
158
|
pre_save = self.class.pre_save_representer.new(self)
|
127
|
-
pre_save.to_hash(:include => pre_save.twin_names) # #save on nested Twins.
|
159
|
+
pre_save.to_hash(:include => pre_save.twin_names, :processed_map => processed_map) # #save on nested Twins.
|
128
160
|
|
129
161
|
|
130
162
|
|
@@ -150,6 +182,9 @@ module Disposable
|
|
150
182
|
attr_reader :model # TODO: test
|
151
183
|
|
152
184
|
|
185
|
+
class ObjectMap < Hash
|
186
|
+
end
|
187
|
+
|
153
188
|
# class Composition < self
|
154
189
|
# def initialize(hash)
|
155
190
|
# hash = hash.first
|
data/lib/disposable/version.rb
CHANGED
data/lib/disposable.rb
CHANGED
@@ -5,8 +5,6 @@ module Disposable
|
|
5
5
|
autoload :Composition, 'disposable/composition'
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
require 'disposable/facade/active_record'
|
12
|
-
end
|
8
|
+
# if defined?(ActiveRecord)
|
9
|
+
# require 'disposable/facade/active_record'
|
10
|
+
# end
|
data/test/composition_test.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
require 'active_record'
|
4
3
|
class Album < ActiveRecord::Base
|
5
4
|
has_many :songs
|
6
5
|
end
|
@@ -33,6 +32,7 @@ class TwinActiveRecordTest < MiniTest::Spec
|
|
33
32
|
class Album < Disposable::Twin
|
34
33
|
property :id
|
35
34
|
property :name
|
35
|
+
collection :songs, :twin => lambda { |*| Twin::Song }
|
36
36
|
|
37
37
|
model ::Album
|
38
38
|
end
|
@@ -40,13 +40,42 @@ class TwinActiveRecordTest < MiniTest::Spec
|
|
40
40
|
class Song < Disposable::Twin
|
41
41
|
property :id
|
42
42
|
property :title
|
43
|
-
property :album, :twin => Album
|
43
|
+
property :album, :twin => Album
|
44
44
|
|
45
45
|
model ::Song
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
49
|
|
50
|
+
# new models
|
51
|
+
describe "::from, nested circular dependency" do
|
52
|
+
let (:song) { ::Song.new(:title => "Broken", :album => album) }
|
53
|
+
let (:album) { ::Album.new(:name => "The Process Of Belief") }
|
54
|
+
|
55
|
+
before { album.songs = [song] } # circular dependency.
|
56
|
+
|
57
|
+
let(:twin) { Twin::Song.from(song) }
|
58
|
+
|
59
|
+
it { twin.album.songs.must_equal [twin] }
|
60
|
+
end
|
61
|
+
|
62
|
+
# existing, nested, models
|
63
|
+
describe "::from existing models, nested circular dependency" do
|
64
|
+
let (:song) { ::Song.create(:title => "Broken", :album => album) }
|
65
|
+
let (:album) { ::Album.create(:name => "The Process Of Belief") }
|
66
|
+
|
67
|
+
before { album.songs.must_equal [song] } # circular dependency.
|
68
|
+
|
69
|
+
let(:twin) { Twin::Song.from(song) }
|
70
|
+
|
71
|
+
it { twin.album.songs.must_equal [twin] }
|
72
|
+
|
73
|
+
it do
|
74
|
+
twin.save
|
75
|
+
twin.album.songs.must_equal [twin]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
50
79
|
|
51
80
|
describe "::find" do
|
52
81
|
let (:song_model) { ::Song.create(:title => "Savage") }
|
@@ -58,6 +87,40 @@ class TwinActiveRecordTest < MiniTest::Spec
|
|
58
87
|
end
|
59
88
|
|
60
89
|
|
90
|
+
describe "::finders" do
|
91
|
+
before {
|
92
|
+
DatabaseCleaner.clean
|
93
|
+
savage
|
94
|
+
starlight
|
95
|
+
}
|
96
|
+
let (:savage) { ::Song.create(:title => "Savage") }
|
97
|
+
let (:starlight) { ::Song.create(:title => "Starlight") }
|
98
|
+
|
99
|
+
describe "collections" do
|
100
|
+
subject { Twin::Song.finders.all }
|
101
|
+
|
102
|
+
it { subject.size.must_equal 2 }
|
103
|
+
|
104
|
+
it { subject[0].must_be_kind_of Twin::Song }
|
105
|
+
it { subject[0].id.must_equal savage.id }
|
106
|
+
it { subject[0].title.must_equal "Savage" }
|
107
|
+
|
108
|
+
it { subject[1].must_be_kind_of Twin::Song }
|
109
|
+
it { subject[1].id.must_equal starlight.id }
|
110
|
+
it { subject[1].title.must_equal "Starlight" }
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "::where" do
|
114
|
+
subject { Twin::Song.finders.where(:title => "Starlight") }
|
115
|
+
|
116
|
+
it { subject.size.must_equal 1 }
|
117
|
+
|
118
|
+
it { subject[0].must_be_kind_of Twin::Song }
|
119
|
+
it { subject[0].id.must_equal starlight.id }
|
120
|
+
it { subject[0].title.must_equal "Starlight" }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
61
124
|
|
62
125
|
describe "::save, nested not set" do
|
63
126
|
let (:twin) { Twin::Song.new(:title => "1.80 Down") }
|
data/test/twin/twin_test.rb
CHANGED
@@ -15,7 +15,7 @@ class TwinTest < MiniTest::Spec
|
|
15
15
|
class Album < Disposable::Twin
|
16
16
|
property :id # DISCUSS: needed for #save.
|
17
17
|
property :name
|
18
|
-
collection :songs, :twin => Song
|
18
|
+
collection :songs, :twin => lambda { |*| Song }
|
19
19
|
|
20
20
|
model Model::Album
|
21
21
|
end
|
@@ -23,7 +23,7 @@ class TwinTest < MiniTest::Spec
|
|
23
23
|
class Song < Disposable::Twin
|
24
24
|
property :id # DISCUSS: needed for #save.
|
25
25
|
property :title
|
26
|
-
property :album, :twin => Album
|
26
|
+
property :album, :twin => Album
|
27
27
|
|
28
28
|
model Model::Song
|
29
29
|
end
|
metadata
CHANGED
@@ -1,111 +1,125 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: disposable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: uber
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
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: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: representable
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ~>
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 2.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 2.0.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ~>
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '1.3'
|
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.3'
|
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: minitest
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - '>='
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
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'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: activerecord
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - '>='
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - '>='
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: sqlite3
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - '>='
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: database_cleaner
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
111
125
|
description: Domain-Oriented Refactoring Framework.
|
@@ -115,8 +129,8 @@ executables: []
|
|
115
129
|
extensions: []
|
116
130
|
extra_rdoc_files: []
|
117
131
|
files:
|
118
|
-
-
|
119
|
-
-
|
132
|
+
- .gitignore
|
133
|
+
- .travis.yml
|
120
134
|
- CHANGES.md
|
121
135
|
- Gemfile
|
122
136
|
- LICENSE.txt
|
@@ -131,13 +145,10 @@ files:
|
|
131
145
|
- gemfiles/Gemfile.rails-3.0.lock
|
132
146
|
- lib/disposable.rb
|
133
147
|
- lib/disposable/composition.rb
|
134
|
-
- lib/disposable/facade.rb
|
135
|
-
- lib/disposable/facade/active_record.rb
|
136
148
|
- lib/disposable/twin.rb
|
149
|
+
- lib/disposable/twin/finders.rb
|
137
150
|
- lib/disposable/version.rb
|
138
|
-
- test/active_record_test.rb
|
139
151
|
- test/composition_test.rb
|
140
|
-
- test/facade_test.rb
|
141
152
|
- test/test_helper.rb
|
142
153
|
- test/twin/active_record_test.rb
|
143
154
|
- test/twin/composition_test.rb
|
@@ -152,26 +163,23 @@ require_paths:
|
|
152
163
|
- lib
|
153
164
|
required_ruby_version: !ruby/object:Gem::Requirement
|
154
165
|
requirements:
|
155
|
-
- -
|
166
|
+
- - '>='
|
156
167
|
- !ruby/object:Gem::Version
|
157
168
|
version: '0'
|
158
169
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
170
|
requirements:
|
160
|
-
- -
|
171
|
+
- - '>='
|
161
172
|
- !ruby/object:Gem::Version
|
162
173
|
version: '0'
|
163
174
|
requirements: []
|
164
175
|
rubyforge_project:
|
165
|
-
rubygems_version: 2.2.
|
176
|
+
rubygems_version: 2.2.2
|
166
177
|
signing_key:
|
167
178
|
specification_version: 4
|
168
179
|
summary: Domain-Oriented Refactoring Framework.
|
169
180
|
test_files:
|
170
|
-
- test/active_record_test.rb
|
171
181
|
- test/composition_test.rb
|
172
|
-
- test/facade_test.rb
|
173
182
|
- test/test_helper.rb
|
174
183
|
- test/twin/active_record_test.rb
|
175
184
|
- test/twin/composition_test.rb
|
176
185
|
- test/twin/twin_test.rb
|
177
|
-
has_rdoc:
|
data/lib/disposable/facade.rb
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
require "delegate"
|
2
|
-
require "uber/inheritable_attr"
|
3
|
-
|
4
|
-
module Disposable
|
5
|
-
class Facade < SimpleDelegator
|
6
|
-
extend Uber::InheritableAttr
|
7
|
-
inheritable_attr :facade_options
|
8
|
-
self.facade_options = [nil, {}]
|
9
|
-
|
10
|
-
|
11
|
-
module Facadable
|
12
|
-
# used in facaded.
|
13
|
-
def facade!(facade_class=nil)
|
14
|
-
facade_class ||= self.class.facade_class
|
15
|
-
facade_class.facade!(self)
|
16
|
-
end
|
17
|
-
|
18
|
-
def facade(facade_class=nil)
|
19
|
-
facade_class ||= self.class.facade_class
|
20
|
-
facade_class.facade(self)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
|
-
class << self
|
26
|
-
def facades(klass, options={})
|
27
|
-
facade_options = [klass, options] # TODO: test.
|
28
|
-
|
29
|
-
self.facade_options = facade_options
|
30
|
-
|
31
|
-
facade_class = self
|
32
|
-
klass.instance_eval do
|
33
|
-
include Facadable
|
34
|
-
@_facade_class = facade_class
|
35
|
-
|
36
|
-
def facade_class
|
37
|
-
@_facade_class
|
38
|
-
end
|
39
|
-
end # TODO: use hooks.
|
40
|
-
end
|
41
|
-
|
42
|
-
# TODO: move both into Facader instance.
|
43
|
-
def facade(facaded)
|
44
|
-
if facade_options.last[:if]
|
45
|
-
return facaded unless facade_options.last[:if].call(facaded)
|
46
|
-
end
|
47
|
-
|
48
|
-
# TODO: check if already facaded.
|
49
|
-
facade!(facaded)
|
50
|
-
end
|
51
|
-
|
52
|
-
def facade!(facaded)
|
53
|
-
new(facaded)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# Forward #id to facaded. this is only a concern in 1.8.
|
58
|
-
def id
|
59
|
-
__getobj__.id
|
60
|
-
end
|
61
|
-
|
62
|
-
|
63
|
-
alias_method :facaded, :__getobj__
|
64
|
-
|
65
|
-
|
66
|
-
# Extend your facade and call Song.build, includes ClassMethods (extend constructor).
|
67
|
-
module Subclass
|
68
|
-
def build(*args)
|
69
|
-
facade_class = self
|
70
|
-
Class.new(facade_options.first).class_eval do
|
71
|
-
include facade_class::InstanceMethods # TODO: check if exists.
|
72
|
-
extend facade_class::ClassMethods # TODO: check if exists.
|
73
|
-
|
74
|
-
self
|
75
|
-
end.new(*args)
|
76
|
-
end
|
77
|
-
|
78
|
-
alias_method :subclass, :build
|
79
|
-
end
|
80
|
-
|
81
|
-
|
82
|
-
module Refine
|
83
|
-
def initialize(facaded) # DISCUSS: should we override ::facade here?
|
84
|
-
super.tap do |res|
|
85
|
-
refine(facaded)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def refine(facaded)
|
90
|
-
facaded.extend(self.class::Refinements)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
data/test/active_record_test.rb
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
require 'active_record'
|
4
|
-
class Invoice < ActiveRecord::Base
|
5
|
-
has_many :invoice_items
|
6
|
-
end
|
7
|
-
|
8
|
-
class InvoiceItem < ActiveRecord::Base
|
9
|
-
belongs_to :invoice
|
10
|
-
end
|
11
|
-
|
12
|
-
ActiveRecord::Base.establish_connection(
|
13
|
-
:adapter => "sqlite3",
|
14
|
-
:database => "#{Dir.pwd}/database.sqlite3"
|
15
|
-
)
|
16
|
-
|
17
|
-
# ActiveRecord::Schema.define do
|
18
|
-
# create_table :invoices do |table|
|
19
|
-
# table.timestamps
|
20
|
-
# end
|
21
|
-
# end
|
22
|
-
|
23
|
-
# ActiveRecord::Schema.define do
|
24
|
-
# create_table :invoice_items do |table|
|
25
|
-
# table.column :invoice_id, :string
|
26
|
-
# table.timestamps
|
27
|
-
# end
|
28
|
-
# end
|
29
|
-
|
30
|
-
# TODO: test auto-loading of Rails assets.
|
31
|
-
require 'disposable/facade/active_record'
|
32
|
-
|
33
|
-
class ActiveRecordAssociationsTest < MiniTest::Spec
|
34
|
-
class Item < Disposable::Facade
|
35
|
-
facades InvoiceItem
|
36
|
-
|
37
|
-
include Disposable::Facade::ActiveRecord
|
38
|
-
end
|
39
|
-
|
40
|
-
let (:invoice) { Invoice.new }
|
41
|
-
it "allows adding facades to associations" do
|
42
|
-
# tests #is_a?
|
43
|
-
InvoiceItem.new.facade.class.must_equal Item
|
44
|
-
InvoiceItem.new.facade.is_a?(InvoiceItem).must_equal true
|
45
|
-
|
46
|
-
invoice.invoice_items << InvoiceItem.new.facade
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
class InvoiceFacade < Disposable::Facade
|
51
|
-
facades ::Invoice
|
52
|
-
|
53
|
-
include Disposable::Facade::ActiveRecord
|
54
|
-
#has_many :items, :class_name => ::InvoiceItem, :foreign_key_name => :invoice_item_id
|
55
|
-
|
56
|
-
module InstanceMethods # IncludeMethods, Included
|
57
|
-
extend ActiveSupport::Concern
|
58
|
-
included do
|
59
|
-
has_many :items, :class_name => ::InvoiceItem, :foreign_key => :invoice_item_id
|
60
|
-
|
61
|
-
def self.name
|
62
|
-
"anon"
|
63
|
-
end
|
64
|
-
|
65
|
-
end
|
66
|
-
end
|
67
|
-
module ClassMethods # ExtendMethods, Extended
|
68
|
-
end
|
69
|
-
|
70
|
-
extend Disposable::Facade::Subclass
|
71
|
-
end
|
72
|
-
|
73
|
-
it "what" do
|
74
|
-
invoice = InvoiceFacade.subclass
|
75
|
-
invoice.items << item = InvoiceItem.new
|
76
|
-
# TODO: test items << Facade::Item.new
|
77
|
-
|
78
|
-
invoice.items.must_equal([item])
|
79
|
-
end
|
80
|
-
end
|
data/test/facade_test.rb
DELETED
@@ -1,128 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class FacadeTest < MiniTest::Spec
|
4
|
-
class Track < ::Track
|
5
|
-
end
|
6
|
-
|
7
|
-
class Song < Disposable::Facade
|
8
|
-
facades Track
|
9
|
-
end
|
10
|
-
|
11
|
-
class Hit < Disposable::Facade
|
12
|
-
end
|
13
|
-
|
14
|
-
let (:track) { Track.new }
|
15
|
-
|
16
|
-
it { track.class.must_equal Track }
|
17
|
-
it { track.facade.class.must_equal Song }
|
18
|
-
|
19
|
-
it "allows passing facade name" do # FIXME: what if track doesn't have Facadable?
|
20
|
-
track.facade(Hit).class.must_equal Hit
|
21
|
-
end
|
22
|
-
# DISCUSS: should this be Hit.facade(track) ?
|
23
|
-
|
24
|
-
describe "::facade" do
|
25
|
-
it { Hit.facade(track).class.must_equal Hit }
|
26
|
-
end
|
27
|
-
|
28
|
-
it "facades only once" do
|
29
|
-
end
|
30
|
-
|
31
|
-
describe "#id" do
|
32
|
-
let (:track) do Track.new.instance_eval {
|
33
|
-
def id; 1; end
|
34
|
-
self }
|
35
|
-
end
|
36
|
-
|
37
|
-
# DISCUSS: this actually tests Facadable.
|
38
|
-
it "calls original" do
|
39
|
-
track.facade.id.must_equal 1
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
it "responds to #facaded" do
|
44
|
-
Song.new(facaded = Object.new).facaded.must_equal facaded
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
class FacadesWithOptionsTest < MiniTest::Spec
|
49
|
-
class Track < ::Track
|
50
|
-
end
|
51
|
-
|
52
|
-
class Song < Disposable::Facade
|
53
|
-
facades Track, :if => lambda { |t| t.title }
|
54
|
-
end
|
55
|
-
|
56
|
-
it { Track.new(:title => "Trudging").facade.class.must_equal Song }
|
57
|
-
it { Track.new.facade.class.must_equal Track }
|
58
|
-
|
59
|
-
describe "#facade!" do
|
60
|
-
it "ignores :if" do
|
61
|
-
Track.new.facade!.class.must_equal Song
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
class SubclassTest < MiniTest::Spec
|
67
|
-
class Track
|
68
|
-
attr_reader :title
|
69
|
-
|
70
|
-
def initialize(options)
|
71
|
-
@title = options[:title] # we only save this key.
|
72
|
-
raise "rename didn't work" if options[:name]
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
#require 'disposable/facade/active_record'
|
77
|
-
class Song < Disposable::Facade
|
78
|
-
facades Track
|
79
|
-
# has_one :album
|
80
|
-
# rename_options
|
81
|
-
|
82
|
-
extend Subclass # do ... end instead of ClassMethods.
|
83
|
-
#include Disposable::Facade::ActiveRecord
|
84
|
-
|
85
|
-
module InstanceMethods
|
86
|
-
# DISCUSS: this could be initializer do .. end
|
87
|
-
def initialize(options)
|
88
|
-
options[:title ] = options.delete(:name)
|
89
|
-
super
|
90
|
-
end
|
91
|
-
end
|
92
|
-
module ClassMethods
|
93
|
-
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
#it { Track.facade(Song).new(:name => "Bombs Away").title.must_equal "Bombs Away" }
|
98
|
-
it { Song.subclass(:name => "Bombs Away").title.must_equal "Bombs Away" }
|
99
|
-
# it "what" do
|
100
|
-
# Song.build(:name => "Bombs Away").facade(Song).is_a?(Track).must_equal true
|
101
|
-
# end
|
102
|
-
end
|
103
|
-
|
104
|
-
|
105
|
-
class RefinementsTest < MiniTest::Spec
|
106
|
-
class Track
|
107
|
-
def length
|
108
|
-
duration.round(2)
|
109
|
-
end
|
110
|
-
|
111
|
-
def duration
|
112
|
-
4.321
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
class Song < Disposable::Facade
|
117
|
-
facades Track
|
118
|
-
include Disposable::Facade::Refine
|
119
|
-
|
120
|
-
module Refinements
|
121
|
-
def duration
|
122
|
-
5.309
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
it { Track.new.facade.length.must_equal 5.31 }
|
128
|
-
end
|