disposable 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f223895880d3cf7e132a9fc7fe289d2364cda94
4
- data.tar.gz: f6fb9b0b50ab1f76f6a76c233bc6a7b764d715c0
3
+ metadata.gz: 90e97c08c2c16f6c4990b4e07aa18181665590f5
4
+ data.tar.gz: cadf5067b7b35c87ec40f2c1f33193d957a9b5ab
5
5
  SHA512:
6
- metadata.gz: b419406d81071ac149d0bb88a4b4165763b752be5530125ddb4a2165b05ffb704087cc5502d37bde78ef75b132bf49b101d9b7ec475ef3754904619f5039cae9
7
- data.tar.gz: 8f18dd625473e5880a7a600698bb36eef37cf1791df67ed59e91c9f0179e168d16a32e201002afa8d3bbb85f75d426b88fb5c28f7958d19404d8a8b1878112a9
6
+ metadata.gz: 8183c7256adf882d8506c53301bb9e9e60f8f0fb6e1d6f55c5da6fdd241667034c120a7209f1042126c241986935c29b9037775f29c4f1d50aa447e75ad4a513
7
+ data.tar.gz: 2222286b902c93dd4948b738ec44eeab397dab55bbb79cf043660d2ecb04eee7a96f4c5b0c0e5c27c3c367936d3e93b453dce5653af17f4a68ecab423189cf09
data/.travis.yml CHANGED
@@ -2,7 +2,6 @@ language: ruby
2
2
  rvm:
3
3
  - 2.0.0
4
4
  - 1.9.3
5
- - 1.8.7
6
5
  gemfile:
7
6
  - gemfiles/Gemfile.rails-3.0
8
7
  - gemfiles/Gemfile.rails-2.3
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
- why?
5
- because i want this to fuckin work: Wallpaper.find(51).update_attributes(enabled:false)
6
- i want to see my fields when opening the class file, a la DataMapper.
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 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".
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 persistant objects, a typical "brother" class could be an ActiveRecord one.
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 persistant brother directly anymore. A twin will wrap it and indirect domain from persistance.
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 persistant ActiveRecord brother class
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 persistant object automatically.
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
- ### Creation Using Finders
61
+ ### Finders
62
+
63
+ You can use any finder/scope defined in your model to create twins.
47
64
 
48
- (to be implemented)
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 persistant brother.
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 persistant brother to save. This works recursively, meaning that nested twins will do the same with their pendant.
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
- * Twins don't know anything about the underlying persistance layer.
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
- * steps of refactoring
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", "~> 1.8.1"
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
@@ -4,3 +4,4 @@ source "http://rubygems.org"
4
4
  gemspec :path => '../'
5
5
 
6
6
  gem 'rails', '2.3.18'
7
+ gem 'nokogiri', '~> 1.5.0'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- disposable (0.0.2)
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
- mini_portile (0.5.3)
21
+ database_cleaner (1.3.0)
22
22
  minitest (5.0.8)
23
- multi_json (1.9.3)
24
- nokogiri (1.6.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.1)
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.4)
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.2)
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.5.3)
38
+ mini_portile (0.6.0)
39
39
  minitest (5.0.8)
40
- multi_json (1.9.3)
41
- nokogiri (1.6.1)
42
- mini_portile (~> 0.5.0)
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.1)
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.4)
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
@@ -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=nil)
52
- model, options = nil, model if model.is_a?(Hash) # sorry but i wanna have the same API as ActiveRecord here.
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
- def self.find(id)
57
- new(_model.find(id))
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 it's Twin.
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| args.binding[:twin].call.new(object) }) }
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 => lambda { |args| send("#{args.binding[:private_name]}") },
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 { |model, args| model.save }
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
@@ -1,3 +1,3 @@
1
1
  module Disposable
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
data/lib/disposable.rb CHANGED
@@ -5,8 +5,6 @@ module Disposable
5
5
  autoload :Composition, 'disposable/composition'
6
6
  end
7
7
 
8
- require "disposable/facade"
9
-
10
- if defined?(ActiveRecord)
11
- require 'disposable/facade/active_record'
12
- end
8
+ # if defined?(ActiveRecord)
9
+ # require 'disposable/facade/active_record'
10
+ # end
@@ -2,7 +2,7 @@ require 'test_helper'
2
2
 
3
3
  class CompositionTest < MiniTest::Spec
4
4
  module Model
5
- Band = Struct.new(:id, :title,)
5
+ Band = Struct.new(:id, :title)
6
6
  Album = Struct.new(:id, :name, :album)
7
7
  end
8
8
 
data/test/test_helper.rb CHANGED
@@ -8,3 +8,8 @@ class Track
8
8
 
9
9
  attr_reader :title
10
10
  end
11
+
12
+
13
+ require 'active_record'
14
+ require 'database_cleaner'
15
+ DatabaseCleaner.strategy = :truncation
@@ -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#, representable: true #, setter: lambda { |v, args| self.album=(Album.from(v)) }
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") }
@@ -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 #, setter: lambda { |v, args| self.album=(Album.from(v)) }
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
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-05-21 00:00:00.000000000 Z
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: 1.8.1
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: 1.8.1
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
- - ".gitignore"
119
- - ".travis.yml"
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.1
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:
@@ -1,8 +0,0 @@
1
- class Disposable::Facade
2
- module ActiveRecord
3
- def is_a?(klass)
4
- # DISCUSS: should we use facade_options here for the class?
5
- klass == __getobj__.class or super
6
- end
7
- end
8
- end
@@ -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
@@ -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