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.
Files changed (47) hide show
  1. data/.gitignore +3 -2
  2. data/Gemfile +8 -0
  3. data/MIT-LICENSE +2 -1
  4. data/README.markdown +39 -271
  5. data/Rakefile +22 -14
  6. data/lib/generators/machinist/install/USAGE +2 -0
  7. data/lib/generators/machinist/install/install_generator.rb +48 -0
  8. data/lib/generators/machinist/install/templates/blueprints.rb +9 -0
  9. data/lib/generators/machinist/install/templates/machinist.rb.erb +10 -0
  10. data/lib/generators/machinist/model/model_generator.rb +13 -0
  11. data/lib/machinist.rb +11 -105
  12. data/lib/machinist/active_record.rb +8 -93
  13. data/lib/machinist/active_record/blueprint.rb +41 -0
  14. data/lib/machinist/active_record/lathe.rb +24 -0
  15. data/lib/machinist/blueprint.rb +89 -0
  16. data/lib/machinist/exceptions.rb +32 -0
  17. data/lib/machinist/lathe.rb +69 -0
  18. data/lib/machinist/machinable.rb +97 -0
  19. data/lib/machinist/shop.rb +52 -0
  20. data/lib/machinist/warehouse.rb +36 -0
  21. data/spec/active_record_spec.rb +100 -169
  22. data/spec/blueprint_spec.rb +74 -0
  23. data/spec/exceptions_spec.rb +20 -0
  24. data/spec/inheritance_spec.rb +104 -0
  25. data/spec/machinable_spec.rb +101 -0
  26. data/spec/shop_spec.rb +94 -0
  27. data/spec/spec_helper.rb +4 -6
  28. data/spec/support/active_record_environment.rb +65 -0
  29. data/spec/warehouse_spec.rb +24 -0
  30. metadata +52 -40
  31. data/.autotest +0 -7
  32. data/FAQ.markdown +0 -18
  33. data/VERSION +0 -1
  34. data/init.rb +0 -2
  35. data/lib/machinist/blueprints.rb +0 -25
  36. data/lib/machinist/data_mapper.rb +0 -83
  37. data/lib/machinist/object.rb +0 -30
  38. data/lib/machinist/sequel.rb +0 -62
  39. data/lib/sham.rb +0 -77
  40. data/machinist.gemspec +0 -72
  41. data/spec/data_mapper_spec.rb +0 -134
  42. data/spec/db/.gitignore +0 -1
  43. data/spec/db/schema.rb +0 -20
  44. data/spec/log/.gitignore +0 -1
  45. data/spec/machinist_spec.rb +0 -190
  46. data/spec/sequel_spec.rb +0 -146
  47. 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 'spec'
5
- require 'sham'
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