disposable 0.0.1 → 0.0.2
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 +8 -0
- data/Gemfile +2 -0
- data/README.md +131 -0
- data/Rakefile +8 -0
- data/STUFF +8 -1
- data/database.sqlite3 +0 -0
- data/disposable.gemspec +6 -0
- data/gemfiles/Gemfile.rails-2.3 +6 -0
- data/gemfiles/Gemfile.rails-2.3.lock +41 -0
- data/gemfiles/Gemfile.rails-3.0 +7 -0
- data/gemfiles/Gemfile.rails-3.0.lock +65 -0
- data/lib/disposable.rb +7 -2
- data/lib/disposable/composition.rb +48 -0
- data/lib/disposable/facade.rb +79 -10
- data/lib/disposable/facade/active_record.rb +11 -0
- data/lib/disposable/twin.rb +135 -0
- data/lib/disposable/version.rb +1 -1
- data/test/active_record_test.rb +80 -0
- data/test/facade_test.rb +128 -0
- data/test/test_helper.rb +10 -0
- data/test/twin/active_record_test.rb +104 -0
- data/test/twin/composition_test.rb +50 -0
- data/test/twin/twin_test.rb +79 -0
- metadata +95 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a86781957ac37581fc914a2f8dc9ca83b67d47e1
|
4
|
+
data.tar.gz: 0a994524733877ec59edb02ce050272b447e9409
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94b995707bba8d65c9361ae3c0b50cfbd23d2c72f2f0212791800cfe8e771a815b7cef80c4d305e9481a3e284959131cc304b3fc2a0fc97076e7d28435de3898
|
7
|
+
data.tar.gz: 6f7fcd2584e0b0d06b2d4615d9d7280d0791591837750c2d515fdb0445de3677e69d397de557d3fc05e181e8877046bb3fa8ae884e7d75c55f207af1f55c537a
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,89 @@
|
|
1
1
|
# Disposable
|
2
2
|
|
3
3
|
|
4
|
+
|
5
|
+
## Twin
|
6
|
+
|
7
|
+
Twins implement light-weight domain objects that contain business logic - no persistance logic. They have read- and write access to a persistant object (or a composition of those) and expose a sub-set of accessors to that persistant "brother", making it a "data mapper-like phantom".
|
8
|
+
|
9
|
+
Being designed to wrap persistant objects, a typical "brother" class could be an ActiveRecord one.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class Song < ActiveRecord::Base
|
13
|
+
belongs_to :album
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
You don't wanna work with that persistant brother directly anymore. A twin will wrap it and indirect domain from persistance.
|
18
|
+
|
19
|
+
The twin itself has a _ridiculous_ simple API.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class Twin::Song < Disposable::Twin
|
23
|
+
model ::Song # the persistant ActiveRecord brother class
|
24
|
+
|
25
|
+
property :title
|
26
|
+
property :album, twin: Album # a nested twin.
|
27
|
+
```
|
28
|
+
|
29
|
+
|
30
|
+
### Creation
|
31
|
+
|
32
|
+
You can create fresh, blank-slate twins yourself. They will create a new persistant object automatically.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
song = Twin::Song.new(title: "Justified Black Eye")
|
36
|
+
|
37
|
+
#=> #<Twin::Song title: "Justified Black Eye">
|
38
|
+
```
|
39
|
+
|
40
|
+
This doesn't involve any database operations at all.
|
41
|
+
|
42
|
+
|
43
|
+
### Creation Using Finders
|
44
|
+
|
45
|
+
(to be implemented)
|
46
|
+
Every `Twin` subclass exposes all finders from the brother class. However, instances from the persistance layer are automatically twin'ed.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
song = Twin::Song.find(1)
|
50
|
+
|
51
|
+
#=> #<Twin::Song title: "Already Won" album: #<Twin::Album>>
|
52
|
+
```
|
53
|
+
|
54
|
+
|
55
|
+
### Read And Write Access
|
56
|
+
|
57
|
+
All attributes declared with `property` are exposed on the twin.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
song.title #=> "Already Won"
|
61
|
+
|
62
|
+
song.album.name #=> "The Songs of Tony Sly: A Tribute"
|
63
|
+
```
|
64
|
+
|
65
|
+
Note that writing to the twin **does not** change any state on the persistant brother.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
song.title = "Still Winning" # no change on Song, no write to DB.
|
69
|
+
```
|
70
|
+
|
71
|
+
|
72
|
+
### Saving
|
73
|
+
|
74
|
+
Calling `#save` will sync all properties to the brother object and advice the persistant brother to save. This works recursively, meaning that nested twins will do the same with their pendant.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
song.save # write to DB.
|
78
|
+
```
|
79
|
+
|
80
|
+
* Twins don't know anything about the underlying persistance layer.
|
81
|
+
* Lazy-loading
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
## To be documented properly
|
86
|
+
|
4
87
|
Facade existing (model) class
|
5
88
|
where to decorate old instances from collections?
|
6
89
|
option.invoice_template => .items
|
@@ -18,6 +101,16 @@ class Invoice
|
|
18
101
|
end
|
19
102
|
|
20
103
|
|
104
|
+
Why facades?
|
105
|
+
you don't wanna add code to existing shit
|
106
|
+
transparently change the API
|
107
|
+
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
|
108
|
+
no inheritance, composition whereever possible (interfaces between layers)
|
109
|
+
optional, still use old assets
|
110
|
+
don't/step-wise change existing "running" code
|
111
|
+
|
112
|
+
you basically don't change existing code but build extracted components on top of your legacy app
|
113
|
+
|
21
114
|
* facades
|
22
115
|
* overriding public methods in facade
|
23
116
|
* temporary Refinements
|
@@ -25,11 +118,49 @@ class Invoice
|
|
25
118
|
* steps of refactoring
|
26
119
|
|
27
120
|
|
121
|
+
"partial refactoring"
|
28
122
|
"explicit refactoring"
|
29
123
|
|
30
124
|
|
125
|
+
* make it simple to split existing model into smaller (reducing validations etc)
|
126
|
+
* mark dead code
|
127
|
+
* by explicit use of `facade` you can track "dirt"
|
128
|
+
|
129
|
+
* rename options => DSL
|
31
130
|
|
32
131
|
TODO: Write a gem description
|
132
|
+
* generator for Facades
|
133
|
+
loading of facades?
|
134
|
+
location of facades? i wanna have Facade::Client, not facades/ClientFacade.
|
135
|
+
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.
|
136
|
+
|
137
|
+
Facadable#facade
|
138
|
+
Facade#facaded
|
139
|
+
|
140
|
+
|
141
|
+
idea: collect # REFAC lines
|
142
|
+
|
143
|
+
|
144
|
+
running with Rails 2.3->4.0, 1.8.7 ..as that makes sense
|
145
|
+
|
146
|
+
|
147
|
+
## Refinement
|
148
|
+
|
149
|
+
injected into instance _after_ construction
|
150
|
+
|
151
|
+
## Build
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
FacadeClass
|
156
|
+
extend Build
|
157
|
+
|
158
|
+
module ClassMethods
|
159
|
+
def initializer
|
160
|
+
def another_class_method # FIXME: does that work?
|
161
|
+
|
162
|
+
make anonymous class, allow overriding initializer and (works?) add class methods.
|
163
|
+
|
33
164
|
|
34
165
|
## Installation
|
35
166
|
|
data/Rakefile
CHANGED
data/STUFF
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
USE CASE:
|
2
2
|
|
3
3
|
<% client.invoices.outstanding.each do |invoice| %>
|
4
|
-
<%= link_to invoice.invoice_number, {:controller => "party", :action => "invoice_show", :id => invoice.id, :entity_instance_id => client.party, :entity_type_id => EntityType::PARTY}, :onclick => "event.cancelBubble=true;" %> <%= number_to_currency(invoice.balance_out)
|
4
|
+
<%= link_to invoice.invoice_number, {:controller => "party", :action => "invoice_show", :id => invoice.id, :entity_instance_id => client.party, :entity_type_id => EntityType::PARTY}, :onclick => "event.cancelBubble=true;" %> <%= number_to_currency(invoice.balance_out)
|
5
|
+
|
6
|
+
|
7
|
+
Invoice::Option::Rate <-- uses option everywhere
|
8
|
+
make that declaratively hook-able:
|
9
|
+
belongs_to Invoice::Option
|
10
|
+
|
11
|
+
Invoice::Option#rate => Rate.new(self)
|
data/database.sqlite3
ADDED
Binary file
|
data/disposable.gemspec
CHANGED
@@ -18,6 +18,12 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_dependency "uber"
|
22
|
+
spec.add_dependency "representable"
|
23
|
+
|
21
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
25
|
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "minitest"
|
27
|
+
spec.add_development_dependency "activerecord"
|
28
|
+
spec.add_development_dependency "sqlite3"
|
23
29
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
PATH
|
2
|
+
remote: /home/nick/projects/disposable
|
3
|
+
specs:
|
4
|
+
disposable (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
actionmailer (2.3.18)
|
10
|
+
actionpack (= 2.3.18)
|
11
|
+
actionpack (2.3.18)
|
12
|
+
activesupport (= 2.3.18)
|
13
|
+
rack (~> 1.1.0)
|
14
|
+
activerecord (2.3.18)
|
15
|
+
activesupport (= 2.3.18)
|
16
|
+
activeresource (2.3.18)
|
17
|
+
activesupport (= 2.3.18)
|
18
|
+
activesupport (2.3.18)
|
19
|
+
minitest (5.0.8)
|
20
|
+
rack (1.1.6)
|
21
|
+
rails (2.3.18)
|
22
|
+
actionmailer (= 2.3.18)
|
23
|
+
actionpack (= 2.3.18)
|
24
|
+
activerecord (= 2.3.18)
|
25
|
+
activeresource (= 2.3.18)
|
26
|
+
activesupport (= 2.3.18)
|
27
|
+
rake (>= 0.8.3)
|
28
|
+
rake (10.1.0)
|
29
|
+
sqlite3 (1.3.8)
|
30
|
+
|
31
|
+
PLATFORMS
|
32
|
+
ruby
|
33
|
+
|
34
|
+
DEPENDENCIES
|
35
|
+
activerecord
|
36
|
+
bundler (~> 1.3)
|
37
|
+
disposable!
|
38
|
+
minitest
|
39
|
+
rails (= 2.3.18)
|
40
|
+
rake
|
41
|
+
sqlite3
|
@@ -0,0 +1,65 @@
|
|
1
|
+
PATH
|
2
|
+
remote: /home/nick/projects/disposable
|
3
|
+
specs:
|
4
|
+
disposable (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
abstract (1.0.0)
|
10
|
+
actionpack (3.0.20)
|
11
|
+
activemodel (= 3.0.20)
|
12
|
+
activesupport (= 3.0.20)
|
13
|
+
builder (~> 2.1.2)
|
14
|
+
erubis (~> 2.6.6)
|
15
|
+
i18n (~> 0.5.0)
|
16
|
+
rack (~> 1.2.5)
|
17
|
+
rack-mount (~> 0.6.14)
|
18
|
+
rack-test (~> 0.5.7)
|
19
|
+
tzinfo (~> 0.3.23)
|
20
|
+
activemodel (3.0.20)
|
21
|
+
activesupport (= 3.0.20)
|
22
|
+
builder (~> 2.1.2)
|
23
|
+
i18n (~> 0.5.0)
|
24
|
+
activerecord (3.0.20)
|
25
|
+
activemodel (= 3.0.20)
|
26
|
+
activesupport (= 3.0.20)
|
27
|
+
arel (~> 2.0.10)
|
28
|
+
tzinfo (~> 0.3.23)
|
29
|
+
activesupport (3.0.20)
|
30
|
+
arel (2.0.10)
|
31
|
+
builder (2.1.2)
|
32
|
+
erubis (2.6.6)
|
33
|
+
abstract (>= 1.0.0)
|
34
|
+
i18n (0.5.0)
|
35
|
+
json (1.8.0)
|
36
|
+
minitest (5.0.8)
|
37
|
+
rack (1.2.8)
|
38
|
+
rack-mount (0.6.14)
|
39
|
+
rack (>= 1.0.0)
|
40
|
+
rack-test (0.5.7)
|
41
|
+
rack (>= 1.0)
|
42
|
+
railties (3.0.20)
|
43
|
+
actionpack (= 3.0.20)
|
44
|
+
activesupport (= 3.0.20)
|
45
|
+
rake (>= 0.8.7)
|
46
|
+
rdoc (~> 3.4)
|
47
|
+
thor (~> 0.14.4)
|
48
|
+
rake (10.1.0)
|
49
|
+
rdoc (3.12.2)
|
50
|
+
json (~> 1.4)
|
51
|
+
sqlite3 (1.3.8)
|
52
|
+
thor (0.14.6)
|
53
|
+
tzinfo (0.3.38)
|
54
|
+
|
55
|
+
PLATFORMS
|
56
|
+
ruby
|
57
|
+
|
58
|
+
DEPENDENCIES
|
59
|
+
activerecord (~> 3.0.11)
|
60
|
+
bundler (~> 1.3)
|
61
|
+
disposable!
|
62
|
+
minitest
|
63
|
+
railties (~> 3.0.11)
|
64
|
+
rake
|
65
|
+
sqlite3
|
data/lib/disposable.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
require "disposable/version"
|
2
2
|
|
3
3
|
module Disposable
|
4
|
-
|
4
|
+
autoload :Twin, 'disposable/twin'
|
5
|
+
autoload :Composition, 'disposable/composition'
|
5
6
|
end
|
6
7
|
|
7
|
-
require "disposable/facade"
|
8
|
+
require "disposable/facade"
|
9
|
+
|
10
|
+
if defined?(ActiveRecord)
|
11
|
+
require 'disposable/facade/active_record'
|
12
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Disposable
|
2
|
+
# Composition delegates accessors to models as per configuration.
|
3
|
+
#
|
4
|
+
# class Album
|
5
|
+
# include Disposable::Composition
|
6
|
+
|
7
|
+
# map( {cd: [:id, :name], band: [:title]} )
|
8
|
+
# end
|
9
|
+
|
10
|
+
# album = Album.new(cd: CD.find(1), band: Band.new)
|
11
|
+
# album.id #=> 1
|
12
|
+
# album.title = "Ten Foot Pole"
|
13
|
+
module Composition
|
14
|
+
def self.included(base)
|
15
|
+
base.extend(ClassMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def map(options)
|
20
|
+
@attr2obj = {} # {song: ["title", "track"], artist: ["name"]}
|
21
|
+
|
22
|
+
options.each do |mdl, meths|
|
23
|
+
create_accessors(mdl, meths)
|
24
|
+
attr_reader mdl
|
25
|
+
|
26
|
+
meths.each { |m| @attr2obj[m.to_s] = mdl }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_accessors(model, methods)
|
31
|
+
accessors = methods.collect { |m| [m, "#{m}="] }.flatten
|
32
|
+
delegate *accessors << {:to => :"#{model}"}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
private
|
38
|
+
def initialize(models)
|
39
|
+
models.each do |name, obj|
|
40
|
+
instance_variable_set(:"@#{name}", obj)
|
41
|
+
end
|
42
|
+
|
43
|
+
@_models = models.values
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader:_models
|
47
|
+
end
|
48
|
+
end
|
data/lib/disposable/facade.rb
CHANGED
@@ -1,25 +1,94 @@
|
|
1
1
|
require "delegate"
|
2
|
+
require "uber/inheritable_attr"
|
2
3
|
|
3
4
|
module Disposable
|
4
5
|
class Facade < SimpleDelegator
|
6
|
+
extend Uber::InheritableAttr
|
7
|
+
inheritable_attr :facade_options
|
8
|
+
self.facade_options = [nil, {}]
|
9
|
+
|
10
|
+
|
5
11
|
module Facadable
|
6
|
-
|
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
|
+
|
7
48
|
# TODO: check if already facaded.
|
8
|
-
|
49
|
+
facade!(facaded)
|
9
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
|
10
60
|
end
|
11
61
|
|
12
|
-
def self.facades(klass)
|
13
|
-
facade_class = self
|
14
62
|
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
+
|
18
81
|
|
19
|
-
|
20
|
-
|
82
|
+
module Refine
|
83
|
+
def initialize(facaded) # DISCUSS: should we override ::facade here?
|
84
|
+
super.tap do |res|
|
85
|
+
refine(facaded)
|
21
86
|
end
|
22
|
-
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def refine(facaded)
|
90
|
+
facaded.extend(self.class::Refinements)
|
91
|
+
end
|
23
92
|
end
|
24
93
|
end
|
25
94
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Disposable::Facade
|
2
|
+
module ActiveRecord
|
3
|
+
def is_a?(klass)
|
4
|
+
# DISCUSS: should we use facade_options here for the class?
|
5
|
+
#return self.class.facade_options.first == klass if self.class.facade_options.first
|
6
|
+
# DISCUSS: make ::facades obligatory?
|
7
|
+
|
8
|
+
klass == __getobj__.class or super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'uber/inheritable_attr'
|
2
|
+
require 'representable/decorator'
|
3
|
+
require 'representable/hash'
|
4
|
+
|
5
|
+
module Disposable
|
6
|
+
class Twin
|
7
|
+
class Decorator < Representable::Decorator
|
8
|
+
include Representable::Hash
|
9
|
+
include AllowSymbols
|
10
|
+
|
11
|
+
# DISCUSS: same in reform, is that a bug in represntable?
|
12
|
+
def self.clone # called in inheritable_attr :representer_class.
|
13
|
+
Class.new(self) # By subclassing, representable_attrs.clone is called.
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.build_config
|
17
|
+
super.extend(ConfigExtensions)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.twin_names
|
21
|
+
representable_attrs.twin_names
|
22
|
+
end
|
23
|
+
|
24
|
+
def twins(&block)
|
25
|
+
clone_config!.
|
26
|
+
find_all { |attr| attr[:form] }.
|
27
|
+
each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
module ConfigExtensions
|
31
|
+
def twin_names
|
32
|
+
find_all { |attr| attr[:twin] }.
|
33
|
+
collect { |attr| attr.name }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class Save < self
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
extend Uber::InheritableAttr
|
47
|
+
inheritable_attr :representer_class
|
48
|
+
self.representer_class = Class.new(Decorator)
|
49
|
+
|
50
|
+
inheritable_attr :_model
|
51
|
+
|
52
|
+
def self.model(name)
|
53
|
+
self._model = name
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.property(name, *args, &block)
|
57
|
+
attr_accessor name
|
58
|
+
|
59
|
+
representer_class.property(name, *args, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.from(model) # TODO: private.
|
63
|
+
new(model)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.new(model=nil)
|
67
|
+
model, options = nil, model if model.is_a?(Hash) # sorry but i wanna have the same API as ActiveRecord here.
|
68
|
+
super(model || _model.new, *[options].compact) # TODO: make this nicer.
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.find(id)
|
72
|
+
new(_model.find(id))
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.save_representer
|
76
|
+
# TODO: do that only at compile-time!
|
77
|
+
save = Class.new(representer_class) # inherit configuration
|
78
|
+
save.representable_attrs.
|
79
|
+
find_all { |attr| attr[:twin] }.
|
80
|
+
each { |attr| attr.merge!(
|
81
|
+
:representable => true) }
|
82
|
+
save
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.new_representer
|
86
|
+
representer = Class.new(representer_class) # inherit configuration
|
87
|
+
representer.representable_attrs.
|
88
|
+
find_all { |attr| attr[:twin] }.
|
89
|
+
each { |attr| attr.merge!(
|
90
|
+
:pass_options => true,
|
91
|
+
:prepare => lambda { |object, args| args.binding[:twin].new(object) }) }
|
92
|
+
representer
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def to_hash(*) # DISCUSS: do we want that here?
|
97
|
+
model
|
98
|
+
end
|
99
|
+
|
100
|
+
# it's important to stress that #save is the only entry point where we hit the database after initialize.
|
101
|
+
def save # use that in Reform::AR.
|
102
|
+
twin_names = self.class.representer_class.twin_names
|
103
|
+
|
104
|
+
raw_attrs = self.class.representer_class.new(self).to_hash
|
105
|
+
save_attrs = raw_attrs.select { |k| twin_names.include?(k) }
|
106
|
+
save_attrs.values.map(&:save)
|
107
|
+
|
108
|
+
|
109
|
+
sync_attrs = self.class.save_representer.new(self).to_hash
|
110
|
+
# this is ORM-specific:
|
111
|
+
model.update_attributes(sync_attrs) # this also does `album: #<Album>`
|
112
|
+
|
113
|
+
# FIXME: sync again, here, or just id?
|
114
|
+
self.id = model.id
|
115
|
+
end
|
116
|
+
|
117
|
+
# below is the code for a representable-style twin:
|
118
|
+
|
119
|
+
# TODO: improve speed when setting up a twin.
|
120
|
+
def initialize(model, options={})
|
121
|
+
@model = model
|
122
|
+
|
123
|
+
# 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.
|
124
|
+
from_hash(self.class.new_representer.new(model).to_hash.
|
125
|
+
merge(options))
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
def from_hash(options={})
|
130
|
+
self.class.representer_class.new(self).from_hash(options)
|
131
|
+
end
|
132
|
+
|
133
|
+
attr_reader :model # TODO: test
|
134
|
+
end
|
135
|
+
end
|
data/lib/disposable/version.rb
CHANGED
@@ -0,0 +1,80 @@
|
|
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
ADDED
@@ -0,0 +1,128 @@
|
|
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
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
class Album < ActiveRecord::Base
|
5
|
+
has_many :songs
|
6
|
+
end
|
7
|
+
|
8
|
+
class Song < ActiveRecord::Base
|
9
|
+
belongs_to :album
|
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 :songs do |table|
|
19
|
+
# table.column :title, :string
|
20
|
+
# table.column :album_id, :integer
|
21
|
+
# table.timestamps
|
22
|
+
# end
|
23
|
+
|
24
|
+
# create_table :albums do |table|
|
25
|
+
# table.column :name, :string
|
26
|
+
# table.timestamps
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
|
30
|
+
|
31
|
+
class TwinActiveRecordTest < MiniTest::Spec
|
32
|
+
module Twin
|
33
|
+
class Album < Disposable::Twin
|
34
|
+
property :id
|
35
|
+
property :name
|
36
|
+
|
37
|
+
model ::Album
|
38
|
+
end
|
39
|
+
|
40
|
+
class Song < Disposable::Twin
|
41
|
+
property :id
|
42
|
+
property :title
|
43
|
+
property :album, :twin => Album#, representable: true #, setter: lambda { |v, args| self.album=(Album.from(v)) }
|
44
|
+
|
45
|
+
model ::Song
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
describe "::find" do
|
52
|
+
let (:song_model) { ::Song.create(:title => "Savage") }
|
53
|
+
subject { Twin::Song.find(song_model.id) }
|
54
|
+
|
55
|
+
it { subject.id.must_equal song_model.id }
|
56
|
+
it { subject.title.must_equal "Savage" }
|
57
|
+
it { subject.album.must_equal nil }
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
describe "::save, nested not set" do
|
63
|
+
let (:twin) { Twin::Song.new(:title => "1.80 Down") }
|
64
|
+
before { twin.save }
|
65
|
+
subject { ::Song.find(twin.id) }
|
66
|
+
|
67
|
+
it { subject.attributes.slice("id", "title").
|
68
|
+
must_equal({"id" => subject.id, "title" => "1.80 Down"}) }
|
69
|
+
|
70
|
+
it { subject.album.must_equal nil }
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
describe "::save, nested present" do
|
75
|
+
let (:song) { ::Song.new(:title => "Broken", :album => album) }
|
76
|
+
let (:album) { ::Album.new(:name => "The Process Of Belief") }
|
77
|
+
|
78
|
+
let(:twin) { Twin::Song.from(song) }
|
79
|
+
|
80
|
+
before { twin.save } # propagate album.save
|
81
|
+
|
82
|
+
subject { ::Song.find(twin.id) }
|
83
|
+
|
84
|
+
it { subject.attributes.slice("id", "title").
|
85
|
+
must_equal({"id" => subject.id, "title" => "Broken"}) }
|
86
|
+
|
87
|
+
it { subject.album.must_equal album }
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
describe "::save, nested new" do
|
92
|
+
let(:twin) { Twin::Song.new(:title => "How It Goes", :album => Twin::Album.new(:name => "Billy Talent")) }
|
93
|
+
|
94
|
+
before { twin.save } # propagate album.save
|
95
|
+
|
96
|
+
subject { ::Song.find(twin.id) }
|
97
|
+
|
98
|
+
it {
|
99
|
+
subject.attributes.slice("id", "title").
|
100
|
+
must_equal({"id" => subject.id, "title" => "How It Goes"}) }
|
101
|
+
|
102
|
+
it { subject.album.attributes.slice("name").must_equal("name" => "Billy Talent") }
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CompositionTest < MiniTest::Spec
|
4
|
+
module Model
|
5
|
+
Band = Struct.new(:id, :title,)
|
6
|
+
Album = Struct.new(:id, :name)
|
7
|
+
end
|
8
|
+
|
9
|
+
module Twin
|
10
|
+
class Album #< Disposable::Twin
|
11
|
+
include Disposable::Composition
|
12
|
+
extend Disposable::Composition::ClassMethods # FIXME.
|
13
|
+
|
14
|
+
# property :id # DISCUSS: needed for #save.
|
15
|
+
# property :name, :on => :album
|
16
|
+
# property :title, :on => :band # as: :band_name
|
17
|
+
|
18
|
+
# model Model::Album
|
19
|
+
|
20
|
+
map( {:album => [:id, :name], :band => [:title]} )
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let (:band) { Model::Band.new(1, "Frenzal Rhomb") }
|
25
|
+
let (:album) { Model::Album.new(2, "Dick Sandwhich") }
|
26
|
+
subject { Twin::Album.new(:album => album, :band => band) }
|
27
|
+
|
28
|
+
# it { subject.id.must_equal 2 }
|
29
|
+
it { subject.name.must_equal "Dick Sandwhich" }
|
30
|
+
it { subject.title.must_equal "Frenzal Rhomb" }
|
31
|
+
|
32
|
+
# it { subject.save }
|
33
|
+
|
34
|
+
it "raises when non-mapped property" do
|
35
|
+
assert_raises NoMethodError do
|
36
|
+
subject.raise_an_exception
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "readers to models" do
|
41
|
+
it { subject.album.object_id.must_equal album.object_id }
|
42
|
+
it { subject.band.object_id.must_equal band.object_id }
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
describe "#_models" do
|
47
|
+
it { subject.send(:_models).must_equal([album, band]) }
|
48
|
+
it { Twin::Album.new(:album => album).send(:_models).must_equal([album]) }
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class TwinTest < MiniTest::Spec
|
5
|
+
module Model
|
6
|
+
Song = Struct.new(:id, :title, :album)
|
7
|
+
Album = Struct.new(:id, :name)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
module Twin
|
12
|
+
class Album < Disposable::Twin
|
13
|
+
property :id # DISCUSS: needed for #save.
|
14
|
+
property :name
|
15
|
+
|
16
|
+
model Model::Album
|
17
|
+
end
|
18
|
+
|
19
|
+
class Song < Disposable::Twin
|
20
|
+
property :id # DISCUSS: needed for #save.
|
21
|
+
property :title
|
22
|
+
property :album, :twin => Album #, setter: lambda { |v, args| self.album=(Album.from(v)) }
|
23
|
+
|
24
|
+
model Model::Song
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
describe "::new" do # TODO: this creates a new model!
|
30
|
+
subject { Twin::Song.new }
|
31
|
+
|
32
|
+
it { subject.title.must_equal nil }
|
33
|
+
it { subject.album.must_equal nil }
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
describe "::new with arguments" do
|
38
|
+
let (:album) { Twin::Album.new(:name => "30 Years") }
|
39
|
+
subject { Twin::Song.new("title" => "Broken", "album" => album) }
|
40
|
+
|
41
|
+
it { subject.title.must_equal "Broken" }
|
42
|
+
it { subject.album.must_equal album }
|
43
|
+
it { subject.album.name.must_equal "30 Years" }
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
describe "::new with :symbols" do
|
48
|
+
subject { Twin::Song.new(:title => "Broken") }
|
49
|
+
|
50
|
+
it { subject.title.must_equal "Broken" }
|
51
|
+
it { subject.album.must_equal nil }
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# DISCUSS: make ::from private.
|
56
|
+
describe "::from" do
|
57
|
+
let (:song) { Model::Song.new(1, "Broken", album) }
|
58
|
+
let (:album) { Model::Album.new(2, "The Process Of Belief") }
|
59
|
+
|
60
|
+
subject {Twin::Song.from(song) }
|
61
|
+
|
62
|
+
it { subject.title.must_equal "Broken" }
|
63
|
+
it { subject.album.must_be_kind_of Twin::Album }
|
64
|
+
it { subject.album.name.must_equal album.name }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
class TwinDecoratorTest < MiniTest::Spec
|
70
|
+
subject { TwinTest::Twin::Song.representer_class }
|
71
|
+
|
72
|
+
it { subject.twin_names.must_equal ["album"] }
|
73
|
+
end
|
74
|
+
|
75
|
+
# from is as close to from_hash as possible
|
76
|
+
# there should be #to in a perfect API, nothing else.
|
77
|
+
|
78
|
+
|
79
|
+
# should #new create empty associated models?
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: uber
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: representable
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: bundler
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +66,48 @@ dependencies:
|
|
38
66
|
- - '>='
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activerecord
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sqlite3
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
41
111
|
description: Domain-Oriented Refactoring Framework.
|
42
112
|
email:
|
43
113
|
- apotonick@gmail.com
|
@@ -46,15 +116,30 @@ extensions: []
|
|
46
116
|
extra_rdoc_files: []
|
47
117
|
files:
|
48
118
|
- .gitignore
|
119
|
+
- .travis.yml
|
49
120
|
- Gemfile
|
50
121
|
- LICENSE.txt
|
51
122
|
- README.md
|
52
123
|
- Rakefile
|
53
124
|
- STUFF
|
125
|
+
- database.sqlite3
|
54
126
|
- disposable.gemspec
|
127
|
+
- gemfiles/Gemfile.rails-2.3
|
128
|
+
- gemfiles/Gemfile.rails-2.3.lock
|
129
|
+
- gemfiles/Gemfile.rails-3.0
|
130
|
+
- gemfiles/Gemfile.rails-3.0.lock
|
55
131
|
- lib/disposable.rb
|
132
|
+
- lib/disposable/composition.rb
|
56
133
|
- lib/disposable/facade.rb
|
134
|
+
- lib/disposable/facade/active_record.rb
|
135
|
+
- lib/disposable/twin.rb
|
57
136
|
- lib/disposable/version.rb
|
137
|
+
- test/active_record_test.rb
|
138
|
+
- test/facade_test.rb
|
139
|
+
- test/test_helper.rb
|
140
|
+
- test/twin/active_record_test.rb
|
141
|
+
- test/twin/composition_test.rb
|
142
|
+
- test/twin/twin_test.rb
|
58
143
|
homepage: ''
|
59
144
|
licenses:
|
60
145
|
- MIT
|
@@ -75,8 +160,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
160
|
version: '0'
|
76
161
|
requirements: []
|
77
162
|
rubyforge_project:
|
78
|
-
rubygems_version: 2.
|
163
|
+
rubygems_version: 2.2.2
|
79
164
|
signing_key:
|
80
165
|
specification_version: 4
|
81
166
|
summary: Domain-Oriented Refactoring Framework.
|
82
|
-
test_files:
|
167
|
+
test_files:
|
168
|
+
- test/active_record_test.rb
|
169
|
+
- test/facade_test.rb
|
170
|
+
- test/test_helper.rb
|
171
|
+
- test/twin/active_record_test.rb
|
172
|
+
- test/twin/composition_test.rb
|
173
|
+
- test/twin/twin_test.rb
|