elight-machinist 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ Autotest.add_hook :initialize do |at|
2
+ at.clear_mappings
3
+
4
+ at.add_mapping(%r%^spec/(.*)_spec.rb$%) {|filename, _| filename }
5
+ at.add_mapping(%r%^lib/(.*).rb$%) {|_, match| "spec/#{match[1]}_spec.rb" }
6
+ at.add_mapping(%r%^spec/spec_helper.rb$%) { at.files_matching(%r%^spec/(.*)_spec.rb$%) }
7
+ end
@@ -0,0 +1 @@
1
+ coverage
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Peter Yandell
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,201 @@
1
+ Machinist
2
+ =========
3
+
4
+ *Fixtures aren't fun. Machinist is.*
5
+
6
+ Machinist lets you construct test data on the fly, but instead of doing this:
7
+
8
+ describe Comment do
9
+ before do
10
+ @user = User.create!(:name => "Test User")
11
+ @post = Post.create!(:title => "Test Post", :author => @user, :body => "Lorem ipsum...")
12
+ @comment = Comment.create!(
13
+ :post => @post, :author_name => "Test Commenter", :author_email => "commenter@example.com",
14
+ :spam => true
15
+ )
16
+ end
17
+
18
+ it "should not include comments marked as spam in the without_spam named scope" do
19
+ Comment.without_spam.should_not include(@comment)
20
+ end
21
+ end
22
+
23
+ you can just do this:
24
+
25
+ describe Comment do
26
+ before do
27
+ @comment = Comment.make(:spam => true)
28
+ end
29
+
30
+ it "should not include comments marked as spam in the without_spam named scope" do
31
+ Comment.without_spam.should_not include(@comment)
32
+ end
33
+ end
34
+
35
+ Machinist generates data for the fields you don't care about, and constructs any necessary associated objects.
36
+
37
+ You tell Machinist how to do this with blueprints:
38
+
39
+ require 'faker'
40
+
41
+ Sham.name { Faker::Name.name }
42
+ Sham.email { Faker::Internet.email }
43
+ Sham.title { Faker::Lorem.sentence }
44
+ Sham.body { Faker::Lorem.paragraph }
45
+
46
+ User.blueprint do
47
+ name { Sham.name }
48
+ end
49
+
50
+ Post.blueprint do
51
+ title { Sham.title }
52
+ author { User.make }
53
+ body { Sham.body }
54
+ end
55
+
56
+ Comment.blueprint do
57
+ post
58
+ author_name { Sham.name }
59
+ author_email { Sham.email }
60
+ body { Sham.body }
61
+ end
62
+
63
+
64
+ Installation
65
+ ------------
66
+
67
+ Install the plugin:
68
+
69
+ ./script/plugin install git://github.com/notahat/machinist.git
70
+
71
+ Create a blueprints.rb in your test (or spec) directory, and require it in your test\_helper.rb (or spec\_helper.rb):
72
+
73
+ require File.expand_path(File.dirname(__FILE__) + "/blueprints")
74
+
75
+ Set Sham to reset before each test. In the `class Test::Unit::TestCase` block in your test\_helper.rb, add:
76
+
77
+ setup { Sham.reset }
78
+
79
+ or, if you're on RSpec, in the `Spec::Runner.configure` block in your spec\_helper.rb, add:
80
+
81
+ config.before(:each) { Sham.reset }
82
+
83
+
84
+ Sham - Generating Attribute Values
85
+ ----------------------------------
86
+
87
+ Sham lets you generate random but repeatable unique attributes values.
88
+
89
+ For example, you could define a way to generate random names as:
90
+
91
+ Sham.name { (1..10).map { ('a'..'z').to_a.rand } }
92
+
93
+ Then, to generate a name, call:
94
+
95
+ Sham.name
96
+
97
+ So why not just define a method? Sham ensures two things for you:
98
+
99
+ 1. You get the same sequence of values each time your test is run
100
+ 2. You don't get any duplicate values
101
+
102
+ Sham works very well with the excellent [Faker gem](http://faker.rubyforge.org/) by Benjamin Curtis. Using this, a much nicer way to generate names is:
103
+
104
+ Sham.name { Faker::Name.name }
105
+
106
+ Sham also supports generating numbered sequences if you prefer.
107
+
108
+ Sham.name {|index| "Name #{index}" }
109
+
110
+ If you want to allow duplicate values for a sham, you can pass the `:unique` option:
111
+
112
+ Sham.coin_toss(:unique => false) { rand(2) == 0 : 'heads' : 'tails' }
113
+
114
+
115
+ Blueprints - Generating ActiveRecord Objects
116
+ --------------------------------------------
117
+
118
+ A blueprint describes how to build a generic object for an ActiveRecord model. The idea is that you let the blueprint take care of constructing all the objects and attributes that you don't care about in your test, leaving you to focus on the just the things that you're testing.
119
+
120
+ A simple blueprint might look like this:
121
+
122
+ Comment.blueprint do
123
+ body "A comment!"
124
+ end
125
+
126
+ Once that's defined, you can construct a comment from this blueprint with:
127
+
128
+ Comment.make
129
+
130
+ Machinist calls `save!` on your ActiveRecord model to create the comment, so it will throw an exception if the blueprint doesn't pass your validations. It also calls `reload` after the `save!`.
131
+
132
+ You can override values defined in the blueprint by passing parameters to make:
133
+
134
+ Comment.make(:body => "A different comment!")
135
+
136
+ Rather than providing a constant value for an attribute, you can use Sham to generate a value for each new object:
137
+
138
+ Sham.body { Faker::Lorem.paragraph }
139
+ Comment.blueprint do
140
+ body { Sham.body }
141
+ end
142
+
143
+ Notice the curly braces around `Sham.body`. If you call `Comment.make` with your own body attribute, this block will not be executed.
144
+
145
+ You can use this same syntax to generate associated objects:
146
+
147
+ Comment.blueprint do
148
+ post { Post.make }
149
+ end
150
+
151
+ If the associated model has the same name as the field, you can abbreviate this to:
152
+
153
+ Comment.blueprint do
154
+ post
155
+ end
156
+
157
+ You can refer to already assigned attributes when constructing a new attribute:
158
+
159
+ Comment.blueprint do
160
+ post
161
+ body { "Comment on " + post.name }
162
+ end
163
+
164
+ You can also override associated objects when calling make:
165
+
166
+ post = Post.make
167
+ 3.times { Comment.make(:post => post) }
168
+
169
+ It's common to need to construct an object with particular attributes, or a particular object graph, in a number of tests. The best way to abstract out the construction is to put something like this in your blueprints.rb:
170
+
171
+ class Post
172
+ def self.make_with_comments(attributes = {})
173
+ Post.make(attributes) do |post|
174
+ 3.times { Comment.make(:post => post) }
175
+ end
176
+ end
177
+ end
178
+
179
+ Note that make can take a block, into which it will pass the newly constructed object.
180
+
181
+ If you want to generate an object graph without saving to the database, use make\_unsaved:
182
+
183
+ Comment.make_unsaved
184
+
185
+ This will generate both the Comment and the associated Post without saving either.
186
+
187
+
188
+ Credits
189
+ -------
190
+
191
+ Written by [Pete Yandell](http://notahat.com/).
192
+
193
+ Contributors:
194
+
195
+ - [Roland Swingler](http://github.com/knaveofdiamonds)
196
+
197
+ Thanks to Thoughtbot's [Factory Girl](http://github.com/thoughtbot/factory_girl/tree/master). Machinist was written because I loved the idea behind Factory Girl, but I thought the philosophy wasn't quite right, and I hated the syntax.
198
+
199
+ ---
200
+
201
+ Copyright (c) 2008 Peter Yandell, released under the MIT license
@@ -0,0 +1,20 @@
1
+ require 'rake'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/clean'
4
+ require 'spec/rake/spectask'
5
+
6
+ desc 'Default: run specs.'
7
+ task :default => :spec
8
+
9
+ desc 'Run all the specs for the machinist plugin.'
10
+ Spec::Rake::SpecTask.new do |t|
11
+ t.spec_files = FileList['spec/**/*_spec.rb']
12
+ t.rcov = false
13
+ end
14
+
15
+ spec = eval(File.read(File.join(File.dirname(__FILE__), 'machinist.gemspec')))
16
+
17
+ Rake::GemPackageTask.new(spec) do |pkg|
18
+ pkg.need_zip = true
19
+ pkg.need_tar = true
20
+ end
data/init.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'active_record'
2
+
3
+ if RAILS_ENV == 'test'
4
+ require 'machinist'
5
+ require 'sham'
6
+
7
+ class ActiveRecord::Base
8
+ include Machinist::ActiveRecordExtensions
9
+ end
10
+ end
@@ -0,0 +1,88 @@
1
+ require 'active_support'
2
+ require 'active_record'
3
+
4
+ module Machinist
5
+ def self.with_save_nerfed
6
+ begin
7
+ @@nerfed = true
8
+ yield
9
+ ensure
10
+ @@nerfed = false
11
+ end
12
+ end
13
+
14
+ @@nerfed = false
15
+ def self.nerfed?
16
+ @@nerfed
17
+ end
18
+
19
+ module ActiveRecordExtensions
20
+ def self.included(base)
21
+ base.extend(ClassMethods)
22
+ end
23
+
24
+ module ClassMethods
25
+ def blueprint(&blueprint)
26
+ @blueprint = blueprint
27
+ end
28
+
29
+ def make(attributes = {})
30
+ raise "No blueprint for class #{self}" if @blueprint.nil?
31
+ lathe = Lathe.new(self.new, attributes)
32
+ lathe.instance_eval(&@blueprint)
33
+ unless Machinist.nerfed?
34
+ lathe.object.save!
35
+ lathe.object.reload
36
+ end
37
+ returning(lathe.object) do |object|
38
+ yield object if block_given?
39
+ end
40
+ end
41
+
42
+ def make_unsaved(attributes = {})
43
+ returning(Machinist.with_save_nerfed { make(attributes) }) do |object|
44
+ yield object if block_given?
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ class Lathe
51
+ def initialize(object, attributes)
52
+ @object = object
53
+ @assigned_attributes = []
54
+ attributes.each do |key, value|
55
+ @object.send("#{key}=", value)
56
+ @assigned_attributes << key
57
+ end
58
+ end
59
+
60
+ attr_reader :object
61
+
62
+ def method_missing(symbol, *args, &block)
63
+ if @assigned_attributes.include?(symbol)
64
+ @object.send(symbol)
65
+ else
66
+ value = if block
67
+ block.call
68
+ elsif args.first.is_a?(Hash) || args.empty?
69
+ symbol.to_s.camelize.constantize.make(args.first || {})
70
+ else
71
+ args.first
72
+ end
73
+ @object.send("#{symbol}=", value)
74
+ @assigned_attributes << symbol
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ if RAILS_ENV == 'test'
81
+ require 'sham'
82
+
83
+ class ActiveRecord::Base
84
+ include Machinist::ActiveRecordExtensions
85
+ end
86
+ end
87
+
88
+
@@ -0,0 +1,65 @@
1
+ require 'active_support'
2
+
3
+ class Sham
4
+ @@shams = {}
5
+
6
+ # Over-ride module's built-in name method, so we can re-use it for
7
+ # generating names. This is a bit of a no-no, but we get away with
8
+ # it in this context.
9
+ def self.name(*args, &block)
10
+ method_missing(:name, *args, &block)
11
+ end
12
+
13
+ def self.method_missing(symbol, *args, &block)
14
+ if block_given?
15
+ @@shams[symbol] = Sham.new(symbol, args.pop || {}, &block)
16
+ else
17
+ sham = @@shams[symbol]
18
+ raise "No sham defined for #{symbol}" if sham.nil?
19
+ sham.fetch_value
20
+ end
21
+ end
22
+
23
+ def self.reset
24
+ @@shams.values.each(&:reset)
25
+ end
26
+
27
+ def initialize(name, options = {}, &block)
28
+ @name = name
29
+ @generator = block
30
+ @offset = 0
31
+ @unique = options.has_key?(:unique) ? options[:unique] : true
32
+ generate_values(12)
33
+ end
34
+
35
+ def reset
36
+ @offset = 0
37
+ end
38
+
39
+ def fetch_value
40
+ # Generate more values if we need them.
41
+ if @offset >= @values.length
42
+ generate_values(2 * @values.length)
43
+ raise "Can't generate more unique values for Sham.#{@name}" if @offset >= @values.length
44
+ end
45
+ returning @values[@offset] do
46
+ @offset += 1
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def generate_values(count)
53
+ @values = seeded { (1..count).map(&@generator) }
54
+ @values.uniq! if @unique
55
+ end
56
+
57
+ def seeded
58
+ begin
59
+ srand(1)
60
+ yield
61
+ ensure
62
+ srand
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,22 @@
1
+ MACHINIST_SPEC = Gem::Specification.new do |s|
2
+ s.name = "machinist"
3
+ s.author = "notahat"
4
+ s.homepage = "http://github.com/notahat/machinist/tree"
5
+ s.version = "1.0.0"
6
+ s.date = "2008-11-22"
7
+ s.summary = "Im in ur specs machining your fixtures"
8
+ s.has_rdoc = true
9
+ s.files = [
10
+ ".autotest",
11
+ ".gitignore",
12
+ "MIT-LICENSE",
13
+ "README.markdown",
14
+ "Rakefile",
15
+ "machinist.gemspec",
16
+ "init.rb",
17
+ "lib/machinist.rb",
18
+ "lib/sham.rb"
19
+ ]
20
+ s.test_files = ["spec/machinist_spec.rb", "spec/sham_spec.rb", "spec/spec_helper.rb"]
21
+ s.add_dependency "activesupport"
22
+ end
@@ -0,0 +1,110 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'machinist'
3
+
4
+ class Base
5
+ include Machinist::ActiveRecordExtensions
6
+
7
+ def save!; @saved = true; end
8
+ def reload; @reloaded = true; self; end
9
+
10
+ def saved?; @saved; end
11
+ def reloaded?; @reloaded; end
12
+ end
13
+
14
+ class Post < Base
15
+ attr_accessor :title
16
+ attr_accessor :body
17
+ end
18
+
19
+ class Comment < Base
20
+ attr_accessor :post
21
+ attr_accessor :author
22
+ attr_accessor :body
23
+ end
24
+
25
+ Post.blueprint do
26
+ title "An Example Post"
27
+ body { "The quick brown fox." }
28
+ end
29
+
30
+ Comment.blueprint do
31
+ post
32
+ author "Fred Bloggs"
33
+ body "Just a comment."
34
+ end
35
+
36
+ describe Machinist do
37
+ describe "make methd" do
38
+ before do
39
+ @post = Post.make
40
+ end
41
+
42
+ it "should set a field from a constant in the blueprint" do
43
+ @post.title.should == "An Example Post"
44
+ end
45
+
46
+ it "should set a field from a block in the blueprint" do
47
+ @post.body.should == "The quick brown fox."
48
+ end
49
+
50
+ it "should save the object" do
51
+ @post.should be_saved
52
+ end
53
+
54
+ it "should reload the object" do
55
+ @post.should be_reloaded
56
+ end
57
+ end
58
+
59
+ describe "make_unsaved method" do
60
+ before do
61
+ @comment = Comment.make_unsaved
62
+ end
63
+
64
+ it "should not save the object" do
65
+ @comment.should_not be_saved
66
+ end
67
+
68
+ it "should not reload the object" do
69
+ @comment.should_not be_reloaded
70
+ end
71
+
72
+ it "should not save associated objects" do
73
+ @comment.post.should_not be_saved
74
+ end
75
+
76
+ it "should not reload associated objects" do
77
+ @comment.post.should_not be_reloaded
78
+ end
79
+ end
80
+
81
+ it "should override a field from the blueprint with a parameter" do
82
+ post = Post.make(:title => "A Different Title")
83
+ post.title.should == "A Different Title"
84
+ end
85
+
86
+ it "should create an associated object for a field with no arguments in the blueprint" do
87
+ comment = Comment.make
88
+ comment.post.should_not be_nil
89
+ end
90
+
91
+ it "should allow passing a block to make" do
92
+ comments = nil
93
+ post = Post.make do |post|
94
+ comments = (1..3).map { Comment.make(:post => post) }
95
+ end
96
+ post.should be_an_instance_of(Post)
97
+ comments.should_not be_nil
98
+ comments.each {|comment| comment.post.should == post }
99
+ end
100
+
101
+ it "should not nerf make within a block passed to make_unsaved" do
102
+ comment = nil
103
+ post = Post.make_unsaved do |post|
104
+ comment = Comment.make(:post => post)
105
+ end
106
+ post.should_not be_saved
107
+ comment.should be_saved
108
+ comment.post.should == post
109
+ end
110
+ end
@@ -0,0 +1,45 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'sham'
3
+
4
+ Sham.random { rand }
5
+ Sham.half_index {|index| index/2 }
6
+ Sham.coin_toss(:unique => false) {|index| index % 2 == 1 ? 'heads' : 'tails' }
7
+ Sham.limited {|index| index%10 }
8
+ Sham.index {|index| index }
9
+ Sham.name {|index| index }
10
+
11
+ describe Sham do
12
+ it "should ensure generated values are unique" do
13
+ values = (1..10).map { Sham.half_index }
14
+ values.should == (0..9).to_a
15
+ end
16
+
17
+ it "should generate non-unique values when asked" do
18
+ values = (1..4).map { Sham.coin_toss }
19
+ values.should == ['heads', 'tails', 'heads', 'tails']
20
+ end
21
+
22
+ it "should generate more than a dozen values" do
23
+ values = (1..25).map { Sham.index }
24
+ values.should == (1..25).to_a
25
+ end
26
+
27
+ it "should generate the same sequence of values after a reset" do
28
+ values1 = (1..10).map { Sham.random }
29
+ Sham.reset
30
+ values2 = (1..10).map { Sham.random }
31
+ values2.should == values1
32
+ end
33
+
34
+ it "should die when it runs out of unique values" do
35
+ lambda {
36
+ (1..100).map { Sham.limited }
37
+ }.should raise_error(RuntimeError)
38
+ end
39
+
40
+ it "should allow over-riding the name method" do
41
+ Sham.name.should == 1
42
+ end
43
+
44
+
45
+ end
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'test/unit'
3
+ require 'spec'
4
+ require 'rubygems'
5
+ require 'sham'
6
+
7
+ Spec::Runner.configure do |config|
8
+ config.before(:each) { Sham.reset }
9
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elight-machinist
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - notahat
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-22 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description:
25
+ email:
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files: []
31
+
32
+ files:
33
+ - .autotest
34
+ - .gitignore
35
+ - MIT-LICENSE
36
+ - README.markdown
37
+ - Rakefile
38
+ - machinist.gemspec
39
+ - init.rb
40
+ - lib/machinist.rb
41
+ - lib/sham.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/notahat/machinist/tree
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.2.0
65
+ signing_key:
66
+ specification_version: 2
67
+ summary: Im in ur specs machining your fixtures
68
+ test_files:
69
+ - spec/machinist_spec.rb
70
+ - spec/sham_spec.rb
71
+ - spec/spec_helper.rb