machinist 1.0.6 → 2.0.0.beta1
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.
- data/.gitignore +3 -2
- data/Gemfile +8 -0
- data/MIT-LICENSE +2 -1
- data/README.markdown +39 -271
- data/Rakefile +22 -14
- data/lib/generators/machinist/install/USAGE +2 -0
- data/lib/generators/machinist/install/install_generator.rb +48 -0
- data/lib/generators/machinist/install/templates/blueprints.rb +9 -0
- data/lib/generators/machinist/install/templates/machinist.rb.erb +10 -0
- data/lib/generators/machinist/model/model_generator.rb +13 -0
- data/lib/machinist.rb +11 -105
- data/lib/machinist/active_record.rb +8 -93
- data/lib/machinist/active_record/blueprint.rb +41 -0
- data/lib/machinist/active_record/lathe.rb +24 -0
- data/lib/machinist/blueprint.rb +89 -0
- data/lib/machinist/exceptions.rb +32 -0
- data/lib/machinist/lathe.rb +69 -0
- data/lib/machinist/machinable.rb +97 -0
- data/lib/machinist/shop.rb +52 -0
- data/lib/machinist/warehouse.rb +36 -0
- data/spec/active_record_spec.rb +100 -169
- data/spec/blueprint_spec.rb +74 -0
- data/spec/exceptions_spec.rb +20 -0
- data/spec/inheritance_spec.rb +104 -0
- data/spec/machinable_spec.rb +101 -0
- data/spec/shop_spec.rb +94 -0
- data/spec/spec_helper.rb +4 -6
- data/spec/support/active_record_environment.rb +65 -0
- data/spec/warehouse_spec.rb +24 -0
- metadata +52 -40
- data/.autotest +0 -7
- data/FAQ.markdown +0 -18
- data/VERSION +0 -1
- data/init.rb +0 -2
- data/lib/machinist/blueprints.rb +0 -25
- data/lib/machinist/data_mapper.rb +0 -83
- data/lib/machinist/object.rb +0 -30
- data/lib/machinist/sequel.rb +0 -62
- data/lib/sham.rb +0 -77
- data/machinist.gemspec +0 -72
- data/spec/data_mapper_spec.rb +0 -134
- data/spec/db/.gitignore +0 -1
- data/spec/db/schema.rb +0 -20
- data/spec/log/.gitignore +0 -1
- data/spec/machinist_spec.rb +0 -190
- data/spec/sequel_spec.rb +0 -146
- data/spec/sham_spec.rb +0 -95
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe Machinist::Blueprint do
|
5
|
+
|
6
|
+
it "should make an object of the given class" do
|
7
|
+
blueprint = Machinist::Blueprint.new(OpenStruct) { }
|
8
|
+
blueprint.make.should be_an(OpenStruct)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should construct an attribute in the blueprint" do
|
12
|
+
blueprint = Machinist::Blueprint.new(OpenStruct) do
|
13
|
+
name { "Fred" }
|
14
|
+
end
|
15
|
+
blueprint.make.name.should == "Fred"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should construct an array for an attribute in the blueprint" do
|
19
|
+
blueprint = Machinist::Blueprint.new(OpenStruct) do
|
20
|
+
things(3) { Object.new }
|
21
|
+
end
|
22
|
+
things = blueprint.make.things
|
23
|
+
things.should be_an(Array)
|
24
|
+
things.should have(3).elements
|
25
|
+
things.each {|thing| thing.should be_an(Object) }
|
26
|
+
things.uniq.should == things
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should allow passing in attributes to override the blueprint" do
|
30
|
+
block_called = false
|
31
|
+
blueprint = Machinist::Blueprint.new(OpenStruct) do
|
32
|
+
name { block_called = true; "Fred" }
|
33
|
+
end
|
34
|
+
blueprint.make(:name => "Bill").name.should == "Bill"
|
35
|
+
block_called.should be_false
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should provide a serial number within the blueprint" do
|
39
|
+
blueprint = Machinist::Blueprint.new(OpenStruct) do
|
40
|
+
name { "Fred #{sn}" }
|
41
|
+
end
|
42
|
+
blueprint.make.name.should == "Fred 0001"
|
43
|
+
blueprint.make.name.should == "Fred 0002"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should provide access to the object being constructed within the blueprint" do
|
47
|
+
blueprint = Machinist::Blueprint.new(OpenStruct) do
|
48
|
+
title { "Test" }
|
49
|
+
body { object.title }
|
50
|
+
end
|
51
|
+
blueprint.make.body.should == "Test"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should allow attribute names to be strings" do
|
55
|
+
blueprint = Machinist::Blueprint.new(OpenStruct) do
|
56
|
+
name { "Fred" }
|
57
|
+
end
|
58
|
+
blueprint.make("name" => "Bill").name.should == "Bill"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should work with type and id attributes" do
|
62
|
+
klass = Class.new do
|
63
|
+
attr_accessor :id, :type
|
64
|
+
end
|
65
|
+
blueprint = Machinist::Blueprint.new(klass) do
|
66
|
+
id { "custom id" }
|
67
|
+
type { "custom type" }
|
68
|
+
end
|
69
|
+
object = blueprint.make
|
70
|
+
object.id.should == "custom id"
|
71
|
+
object.type.should == "custom type"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Machinist, "exceptions" do
|
4
|
+
|
5
|
+
describe Machinist::BlueprintCantSaveError do
|
6
|
+
it "should present the right message" do
|
7
|
+
blueprint = Machinist::Blueprint.new(String) { }
|
8
|
+
exception = Machinist::BlueprintCantSaveError.new(blueprint)
|
9
|
+
exception.message.should == "make! is not supported by blueprints for class String"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Machinist::NoBlueprintError do
|
14
|
+
it "should present the right message" do
|
15
|
+
exception = Machinist::NoBlueprintError.new(String, :master)
|
16
|
+
exception.message.should == "No master blueprint defined for class String"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module InheritanceSpecs
|
5
|
+
class Grandpa
|
6
|
+
extend Machinist::Machinable
|
7
|
+
attr_accessor :name, :age
|
8
|
+
end
|
9
|
+
|
10
|
+
class Dad < Grandpa
|
11
|
+
extend Machinist::Machinable
|
12
|
+
attr_accessor :name, :age
|
13
|
+
end
|
14
|
+
|
15
|
+
class Son < Dad
|
16
|
+
extend Machinist::Machinable
|
17
|
+
attr_accessor :name, :age
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe Machinist::Blueprint do
|
22
|
+
|
23
|
+
describe "explicit inheritance" do
|
24
|
+
it "should inherit attributes from the parent blueprint" do
|
25
|
+
parent_blueprint = Machinist::Blueprint.new(OpenStruct) do
|
26
|
+
name { "Fred" }
|
27
|
+
age { 97 }
|
28
|
+
end
|
29
|
+
|
30
|
+
child_blueprint = Machinist::Blueprint.new(OpenStruct, :parent => parent_blueprint) do
|
31
|
+
name { "Bill" }
|
32
|
+
end
|
33
|
+
|
34
|
+
child = child_blueprint.make
|
35
|
+
child.name.should == "Bill"
|
36
|
+
child.age.should == 97
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should take the serial number from the parent" do
|
40
|
+
parent_blueprint = Machinist::Blueprint.new(OpenStruct) do
|
41
|
+
parent_serial { sn }
|
42
|
+
end
|
43
|
+
|
44
|
+
child_blueprint = Machinist::Blueprint.new(OpenStruct, :parent => parent_blueprint) do
|
45
|
+
child_serial { sn }
|
46
|
+
end
|
47
|
+
|
48
|
+
parent_blueprint.make.parent_serial.should == "0001"
|
49
|
+
child_blueprint.make.child_serial.should == "0002"
|
50
|
+
parent_blueprint.make.parent_serial.should == "0003"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "class inheritance" do
|
55
|
+
before(:each) do
|
56
|
+
[InheritanceSpecs::Grandpa, InheritanceSpecs::Dad, InheritanceSpecs::Son].each(&:clear_blueprints!)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should inherit blueprinted attributes from the parent class" do
|
60
|
+
InheritanceSpecs::Dad.blueprint do
|
61
|
+
name { "Fred" }
|
62
|
+
end
|
63
|
+
InheritanceSpecs::Son.blueprint { }
|
64
|
+
InheritanceSpecs::Son.make.name.should == "Fred"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should override blueprinted attributes in the child class" do
|
68
|
+
InheritanceSpecs::Dad.blueprint do
|
69
|
+
name { "Fred" }
|
70
|
+
end
|
71
|
+
InheritanceSpecs::Son.blueprint do
|
72
|
+
name { "George" }
|
73
|
+
end
|
74
|
+
InheritanceSpecs::Dad.make.name.should == "Fred"
|
75
|
+
InheritanceSpecs::Son.make.name.should == "George"
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should inherit from blueprinted attributes in ancestor class" do
|
79
|
+
InheritanceSpecs::Grandpa.blueprint do
|
80
|
+
name { "Fred" }
|
81
|
+
end
|
82
|
+
InheritanceSpecs::Son.blueprint { }
|
83
|
+
InheritanceSpecs::Grandpa.make.name.should == "Fred"
|
84
|
+
lambda { InheritanceSpecs::Dad.make }.should raise_error(RuntimeError)
|
85
|
+
InheritanceSpecs::Son.make.name.should == "Fred"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should follow inheritance for named blueprints correctly" do
|
89
|
+
InheritanceSpecs::Dad.blueprint do
|
90
|
+
name { "John" }
|
91
|
+
age { 56 }
|
92
|
+
end
|
93
|
+
InheritanceSpecs::Dad.blueprint(:special) do
|
94
|
+
name { "Paul" }
|
95
|
+
end
|
96
|
+
InheritanceSpecs::Son.blueprint(:special) do
|
97
|
+
age { 37 }
|
98
|
+
end
|
99
|
+
InheritanceSpecs::Son.make(:special).name.should == "John"
|
100
|
+
InheritanceSpecs::Son.make(:special).age.should == 37
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
module MachinableSpecs
|
4
|
+
class Post
|
5
|
+
extend Machinist::Machinable
|
6
|
+
attr_accessor :title, :body, :comments
|
7
|
+
end
|
8
|
+
|
9
|
+
class Comment
|
10
|
+
extend Machinist::Machinable
|
11
|
+
attr_accessor :post, :title
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Machinist::Machinable do
|
16
|
+
|
17
|
+
before(:each) do
|
18
|
+
MachinableSpecs::Post.clear_blueprints!
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should make an object" do
|
22
|
+
MachinableSpecs::Post.blueprint do
|
23
|
+
title { "First Post" }
|
24
|
+
end
|
25
|
+
|
26
|
+
post = MachinableSpecs::Post.make
|
27
|
+
post.should be_a(MachinableSpecs::Post)
|
28
|
+
post.title.should == "First Post"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should make an object from a named blueprint" do
|
32
|
+
MachinableSpecs::Post.blueprint do
|
33
|
+
title { "First Post" }
|
34
|
+
body { "Woot!" }
|
35
|
+
end
|
36
|
+
|
37
|
+
MachinableSpecs::Post.blueprint(:extra) do
|
38
|
+
title { "Extra!" }
|
39
|
+
end
|
40
|
+
|
41
|
+
post = MachinableSpecs::Post.make(:extra)
|
42
|
+
post.should be_a(MachinableSpecs::Post)
|
43
|
+
post.title.should == "Extra!"
|
44
|
+
post.body.should == "Woot!"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should make an array of objects" do
|
48
|
+
MachinableSpecs::Post.blueprint do
|
49
|
+
title { "First Post" }
|
50
|
+
end
|
51
|
+
|
52
|
+
posts = MachinableSpecs::Post.make(3)
|
53
|
+
posts.should be_an(Array)
|
54
|
+
posts.should have(3).elements
|
55
|
+
posts.each do |post|
|
56
|
+
post.should be_a(MachinableSpecs::Post)
|
57
|
+
post.title.should == "First Post"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should make array attributes from the blueprint" do
|
62
|
+
MachinableSpecs::Comment.blueprint { }
|
63
|
+
MachinableSpecs::Post.blueprint do
|
64
|
+
comments(3) { MachinableSpecs::Comment.make }
|
65
|
+
end
|
66
|
+
|
67
|
+
post = MachinableSpecs::Post.make
|
68
|
+
post.comments.should be_a(Array)
|
69
|
+
post.comments.should have(3).elements
|
70
|
+
post.comments.each do |comment|
|
71
|
+
comment.should be_a(MachinableSpecs::Comment)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should fail without a blueprint" do
|
76
|
+
expect do
|
77
|
+
MachinableSpecs::Post.make
|
78
|
+
end.should raise_error(Machinist::NoBlueprintError) do |exception|
|
79
|
+
exception.klass.should == MachinableSpecs::Post
|
80
|
+
exception.name.should == :master
|
81
|
+
end
|
82
|
+
|
83
|
+
expect do
|
84
|
+
MachinableSpecs::Post.make(:some_name)
|
85
|
+
end.should raise_error(Machinist::NoBlueprintError) do |exception|
|
86
|
+
exception.klass.should == MachinableSpecs::Post
|
87
|
+
exception.name.should == :some_name
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should fail when calling make! on an unsavable object" do
|
92
|
+
MachinableSpecs::Post.blueprint { }
|
93
|
+
|
94
|
+
expect do
|
95
|
+
MachinableSpecs::Post.make!
|
96
|
+
end.should raise_error(Machinist::BlueprintCantSaveError) do |exception|
|
97
|
+
exception.blueprint.klass.should == MachinableSpecs::Post
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
data/spec/shop_spec.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'support/active_record_environment'
|
3
|
+
|
4
|
+
describe Machinist::Shop do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@shop = Machinist::Shop.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def fake_a_test
|
11
|
+
ActiveRecord::Base.transaction do
|
12
|
+
@shop.restock
|
13
|
+
yield
|
14
|
+
raise ActiveRecord::Rollback
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should cache an object" do
|
19
|
+
blueprint = Machinist::ActiveRecord::Blueprint.new(Post) { }
|
20
|
+
|
21
|
+
post_a, post_b = nil, nil
|
22
|
+
fake_a_test { post_a = @shop.buy(blueprint) }
|
23
|
+
fake_a_test { post_b = @shop.buy(blueprint) }
|
24
|
+
|
25
|
+
post_b.should == post_a
|
26
|
+
post_b.should_not equal(post_a)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should cache an object with attributes" do
|
30
|
+
blueprint = Machinist::ActiveRecord::Blueprint.new(Post) { }
|
31
|
+
|
32
|
+
post_a, post_b = nil, nil
|
33
|
+
fake_a_test { post_a = @shop.buy(blueprint, :title => "Test Title") }
|
34
|
+
fake_a_test { post_b = @shop.buy(blueprint, :title => "Test Title") }
|
35
|
+
|
36
|
+
post_b.should == post_a
|
37
|
+
post_b.should_not equal(post_a)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should not confuse objects with different blueprints" do
|
41
|
+
blueprint_a = Machinist::ActiveRecord::Blueprint.new(Post) { }
|
42
|
+
blueprint_b = Machinist::ActiveRecord::Blueprint.new(Post) { }
|
43
|
+
|
44
|
+
post_a, post_b = nil, nil
|
45
|
+
fake_a_test { post_a = @shop.buy(blueprint_a) }
|
46
|
+
fake_a_test { post_b = @shop.buy(blueprint_b) }
|
47
|
+
|
48
|
+
post_b.should_not == post_a
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not confuse objects with the same blueprint but different attributes" do
|
52
|
+
blueprint = Machinist::ActiveRecord::Blueprint.new(Post) { }
|
53
|
+
|
54
|
+
post_a, post_b = nil, nil
|
55
|
+
fake_a_test { post_a = @shop.buy(blueprint, :title => "A Title") }
|
56
|
+
fake_a_test { post_b = @shop.buy(blueprint, :title => "Not A Title") }
|
57
|
+
|
58
|
+
post_b.should_not == post_a
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should cache multiple similar objects" do
|
62
|
+
blueprint = Machinist::ActiveRecord::Blueprint.new(Post) { }
|
63
|
+
|
64
|
+
post_a, post_b = nil, nil
|
65
|
+
fake_a_test do
|
66
|
+
post_a = @shop.buy(blueprint, :title => "Test Title")
|
67
|
+
post_b = @shop.buy(blueprint, :title => "Test Title")
|
68
|
+
post_b.should_not == post_a
|
69
|
+
end
|
70
|
+
|
71
|
+
fake_a_test do
|
72
|
+
@shop.buy(blueprint, :title => "Test Title").should == post_a
|
73
|
+
@shop.buy(blueprint, :title => "Test Title").should == post_b
|
74
|
+
post_c = @shop.buy(blueprint, :title => "Test Title")
|
75
|
+
post_c.should_not == post_a
|
76
|
+
post_c.should_not == post_b
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should ensure future copies of a cached object do not reflect changes to the original" do
|
81
|
+
blueprint = Machinist::ActiveRecord::Blueprint.new(Post) { }
|
82
|
+
|
83
|
+
post_a, post_b = nil, nil
|
84
|
+
fake_a_test do
|
85
|
+
post_a = @shop.buy(blueprint, :title => "Test Title")
|
86
|
+
post_a.title = "Changed Title"
|
87
|
+
post_a.save!
|
88
|
+
end
|
89
|
+
fake_a_test { post_b = @shop.buy(blueprint, :title => "Test Title") }
|
90
|
+
|
91
|
+
post_b.title.should == "Test Title"
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
3
|
+
|
2
4
|
require 'rubygems'
|
3
5
|
require 'test/unit'
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
|
7
|
-
Spec::Runner.configure do |config|
|
8
|
-
config.before(:each) { Sham.reset }
|
9
|
-
end
|
6
|
+
require 'rspec'
|
7
|
+
require 'machinist'
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'machinist/active_record'
|
3
|
+
|
4
|
+
ActiveRecord::Base.establish_connection(
|
5
|
+
:adapter => "mysql",
|
6
|
+
:database => "machinist",
|
7
|
+
:username => "root",
|
8
|
+
:password => ""
|
9
|
+
)
|
10
|
+
|
11
|
+
ActiveRecord::Schema.define(:version => 0) do
|
12
|
+
create_table :users, :force => true do |t|
|
13
|
+
t.column :username, :string
|
14
|
+
end
|
15
|
+
|
16
|
+
create_table :posts, :force => true do |t|
|
17
|
+
t.column :title, :string
|
18
|
+
t.column :author_id, :integer
|
19
|
+
t.column :body, :text
|
20
|
+
end
|
21
|
+
|
22
|
+
create_table :comments, :force => true do |t|
|
23
|
+
t.column :post_id, :integer
|
24
|
+
t.column :body, :text
|
25
|
+
end
|
26
|
+
|
27
|
+
create_table :tags, :force => true do |t|
|
28
|
+
t.column :name, :string
|
29
|
+
end
|
30
|
+
|
31
|
+
create_table :posts_tags, :id => false, :force => true do |t|
|
32
|
+
t.column :post_id, :integer
|
33
|
+
t.column :tag_id, :integer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class User < ActiveRecord::Base
|
38
|
+
validates_presence_of :username
|
39
|
+
validates_uniqueness_of :username
|
40
|
+
end
|
41
|
+
|
42
|
+
class Post < ActiveRecord::Base
|
43
|
+
has_many :comments
|
44
|
+
belongs_to :author, :class_name => "User"
|
45
|
+
has_and_belongs_to_many :tags
|
46
|
+
end
|
47
|
+
|
48
|
+
class Comment < ActiveRecord::Base
|
49
|
+
belongs_to :post
|
50
|
+
end
|
51
|
+
|
52
|
+
class Tag < ActiveRecord::Base
|
53
|
+
has_and_belongs_to_many :posts
|
54
|
+
end
|
55
|
+
|
56
|
+
module ActiveRecordEnvironment
|
57
|
+
|
58
|
+
def empty_database!
|
59
|
+
[User, Post, Comment].each do |klass|
|
60
|
+
klass.delete_all
|
61
|
+
klass.clear_blueprints!
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|