snepo-dm-machinist 0.1.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Cameron Barrie
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.
data/README ADDED
@@ -0,0 +1,9 @@
1
+ dm-machinist
2
+ =========
3
+
4
+ DataMapper plugin for Pete Yandell's(http://notahat.com/) excellent Machinist library.
5
+
6
+ require 'dm-machinist' in your spec_helper.rb and you can use it in your specs.
7
+
8
+ See Tim Lucus's sweet write up for usage details on Machinist.
9
+ http://toolmantim.com/article/2008/10/27/fixtureless_datas_with_machinist_and_sham
data/Rakefile ADDED
@@ -0,0 +1,80 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'spec/rake/spectask'
6
+ require 'pathname'
7
+
8
+ CLEAN.include '{log,pkg}/'
9
+
10
+ spec = Gem::Specification.new do |s|
11
+ s.name = 'dm-machinist'
12
+ s.version = '0.1.0'
13
+ s.platform = Gem::Platform::RUBY
14
+ s.has_rdoc = true
15
+ s.extra_rdoc_files = %w[ README LICENSE TODO ]
16
+ s.summary = 'DataMapper plugin for machinist'
17
+ s.description = s.summary
18
+ s.author = 'Cameron Barrie'
19
+ s.email = 'cameron@snepo'
20
+ s.homepage = 'http://github.com/snepo/dm-machinist/tree/master'
21
+ s.require_path = 'lib'
22
+ s.files = FileList[ '{lib,spec}/**/*.rb', 'spec/spec.opts', 'Rakefile', *s.extra_rdoc_files ]
23
+ s.add_dependency('dm-core')
24
+ end
25
+
26
+ task :default => [ :spec ]
27
+
28
+ WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
29
+ SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
30
+
31
+ Rake::GemPackageTask.new(spec) do |pkg|
32
+ pkg.gem_spec = spec
33
+ end
34
+
35
+ desc "Generate GemSpec for github"
36
+ task :gemspec do
37
+ spec_text = <<EOF
38
+ Gem::Specification.new do |s|
39
+ s.name = #{spec.name.inspect}
40
+ s.version = '#{spec.version}'
41
+ s.platform = Gem::Platform::RUBY
42
+ s.has_rdoc = #{spec.has_rdoc.inspect}
43
+ s.extra_rdoc_files = #{spec.extra_rdoc_files.inspect}
44
+ s.summary = #{spec.summary.inspect}
45
+ s.description = #{spec.description.inspect}
46
+ s.author = #{spec.author.inspect}
47
+ s.email = #{spec.email.inspect}
48
+ s.homepage = #{spec.homepage.inspect}
49
+ s.require_path = #{spec.require_path.inspect}
50
+ s.files = #{spec.files.inspect}
51
+ s.add_dependency('dm-core')
52
+ end
53
+ EOF
54
+ File.open("dm-machinist.gemspec", "w") do |f|
55
+ f.write(spec_text)
56
+ end
57
+ end
58
+
59
+ desc "Install #{spec.name} #{spec.version} (default ruby)"
60
+ task :install => [ :package ] do
61
+ sh "#{SUDO} gem install --local pkg/#{spec.name}-#{spec.version} --no-update-sources", :verbose => false
62
+ end
63
+
64
+ desc "Uninstall #{spec.name} #{spec.version} (default ruby)"
65
+ task :uninstall => [ :clobber ] do
66
+ sh "#{SUDO} gem uninstall #{spec.name} -v#{spec.version} -I -x", :verbose => false
67
+ end
68
+
69
+ namespace :jruby do
70
+ desc "Install #{spec.name} #{spec.version} with JRuby"
71
+ task :install => [ :package ] do
72
+ sh %{#{SUDO} jruby -S gem install --local pkg/#{spec.name}-#{spec.version} --no-update-sources}, :verbose => false
73
+ end
74
+ end
75
+
76
+ desc 'Run specifications'
77
+ Spec::Rake::SpecTask.new(:spec) do |t|
78
+ t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
79
+ t.spec_files = Pathname.glob(Pathname.new(__FILE__).dirname + 'spec/**/*_spec.rb')
80
+ end
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ TODO
2
+ ====
3
+ Lots of stuff...
4
+ 1. Specing and completing of the Sham class
5
+ 2. ...
@@ -0,0 +1,30 @@
1
+ # Needed to import datamapper and other gems
2
+ require 'rubygems'
3
+ require 'pathname'
4
+
5
+ # Add all external dependencies for the plugin here
6
+ gem 'dm-core', '>=0.9.6'
7
+ gem 'dm-aggregates', '>=0.9.6'
8
+ require 'dm-core'
9
+ require 'dm-aggregates'
10
+
11
+ # Require plugin-files
12
+ require Pathname(__FILE__).dirname.expand_path / 'dm-machinist' / 'machinist.rb'
13
+ require Pathname(__FILE__).dirname.expand_path / 'dm-machinist' / 'sham.rb'
14
+
15
+ # From ActiveSupport
16
+ class Object
17
+ def returning(value)
18
+ yield(value)
19
+ value
20
+ end
21
+ end
22
+
23
+ # Include the plugin in Resource
24
+ module DataMapper
25
+ module Resource
26
+ module ClassMethods
27
+ include DataMapper::Machinist::DataMapperExtentions
28
+ end # module ClassMethods
29
+ end # module Resource
30
+ end # module DataMapper
@@ -0,0 +1,72 @@
1
+ module DataMapper
2
+ module Machinist
3
+ def self.with_save_nerfed
4
+ begin
5
+ @@nerfed = true
6
+ yield
7
+ ensure
8
+ @@nerfed = false
9
+ end
10
+ end
11
+
12
+ @@nerfed = false
13
+ def self.nerfed?
14
+ @@nerfed
15
+ end
16
+
17
+ module DataMapperExtentions
18
+ def blueprint(&blueprint)
19
+ @blueprint = blueprint
20
+ end
21
+
22
+ def make(attributes = {})
23
+ raise "No blueprint for class #{self}" if @blueprint.nil?
24
+ lathe = Lathe.new(self, attributes)
25
+ lathe.instance_eval(&@blueprint)
26
+ unless Machinist.nerfed?
27
+ lathe.object.save
28
+ lathe.object.reload
29
+ end
30
+ returning(lathe.object) do |object|
31
+ yield object if block_given?
32
+ end
33
+ end
34
+
35
+ def make_unsaved(attributes = {})
36
+ returning(Machinist.with_save_nerfed { make(attributes) }) do |object|
37
+ yield object if block_given?
38
+ end
39
+ end
40
+
41
+ class Lathe
42
+ def initialize(object, attributes)
43
+ @object = object.new
44
+ @assigned_attributes = []
45
+ attributes.each do |key, value|
46
+ @object.send("#{key}=", value)
47
+ @assigned_attributes << key
48
+ end
49
+ end
50
+
51
+ attr_reader :object
52
+
53
+ def method_missing(symbol, *args, &block)
54
+ if @assigned_attributes.include?(symbol)
55
+ @object.send(symbol)
56
+ else
57
+ value = if block
58
+ block.call
59
+ elsif args.first.is_a?(Hash) || args.empty?
60
+ symbol.to_s.camelize.constantize.make(args.first || {})
61
+ else
62
+ args.first
63
+ end
64
+ @object.send("#{symbol}=", value)
65
+ @assigned_attributes << symbol
66
+ end
67
+ end
68
+ end #Lathe
69
+
70
+ end #DataMapperExtentions
71
+ end #Machinist
72
+ end #DataMapper
@@ -0,0 +1,63 @@
1
+ class Sham
2
+ @@shams = {}
3
+
4
+ # Over-ride module's built-in name method, so we can re-use it for
5
+ # generating names. This is a bit of a no-no, but we get away with
6
+ # it in this context.
7
+ def self.name(*args, &block)
8
+ method_missing(:name, *args, &block)
9
+ end
10
+
11
+ def self.method_missing(symbol, *args, &block)
12
+ if block_given?
13
+ @@shams[symbol] = Sham.new(symbol, args.pop || {}, &block)
14
+ else
15
+ sham = @@shams[symbol]
16
+ raise "No sham defined for #{symbol}" if sham.nil?
17
+ sham.fetch_value
18
+ end
19
+ end
20
+
21
+ def self.reset
22
+ @@shams.values.each{ |v| v.reset }
23
+ end
24
+
25
+ def initialize(name, options = {}, &block)
26
+ @name = name
27
+ @generator = block
28
+ @offset = 0
29
+ @unique = options.has_key?(:unique) ? options[:unique] : true
30
+ generate_values(12)
31
+ end
32
+
33
+ def reset
34
+ @offset = 0
35
+ end
36
+
37
+ def fetch_value
38
+ # Generate more values if we need them.
39
+ if @offset >= @values.length
40
+ generate_values(2 * @values.length)
41
+ raise "Can't generate more unique values for Sham.#{@name}" if @offset >= @values.length
42
+ end
43
+ returning @values[@offset] do
44
+ @offset += 1
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def generate_values(count)
51
+ @values = seeded { (1..count).map(&@generator) }
52
+ @values.uniq! if @unique
53
+ end
54
+
55
+ def seeded
56
+ begin
57
+ srand(1)
58
+ yield
59
+ ensure
60
+ srand
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ class Comment
2
+ include DataMapper::Resource
3
+ property :id, Serial
4
+ property :author, String
5
+ property :body, Text
6
+ belongs_to :post
7
+ end
data/spec/data/post.rb ADDED
@@ -0,0 +1,7 @@
1
+ class Post
2
+ include DataMapper::Resource
3
+ property :id, Serial
4
+ property :title, String
5
+ property :body, Text
6
+ has n, :comments
7
+ end
@@ -0,0 +1,137 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require File.dirname(__FILE__) + "/../lib/" + 'dm-machinist'
3
+
4
+ Post.blueprint do
5
+ title "An Example Post"
6
+ body { "The quick brown fox." }
7
+ end
8
+
9
+ Comment.blueprint do
10
+ post
11
+ author "Fred Bloggs"
12
+ body "Just a comment."
13
+ end
14
+
15
+
16
+
17
+
18
+ describe "Machinist" do
19
+
20
+ class BeSaved
21
+
22
+ def matches?(target)
23
+ @target = target
24
+ !@target.dirty?
25
+ end
26
+
27
+ def failure_message
28
+ "expected the object to be saved but it's not"
29
+ end
30
+
31
+ def negative_failure_message
32
+ "expected the object not to be saved but is is"
33
+ end
34
+ end
35
+
36
+ def be_saved
37
+ BeSaved.new
38
+ end
39
+
40
+ # class BeReloaded
41
+ # def matches?(target)
42
+ # @target = target
43
+ # @target.methods.each{|m| p m}
44
+ # end
45
+ #
46
+ # def failure_message
47
+ # "expected the object to be reloaded but it's not"
48
+ # end
49
+ #
50
+ # def negative_failure_message
51
+ # "expected the object to not be reloaded but it has been"
52
+ # end
53
+ # end
54
+ #
55
+ # def be_reloaded
56
+ # BeReloaded.new
57
+ # end
58
+
59
+ describe "make methd" do
60
+ before do
61
+ @post = Post.make
62
+ end
63
+
64
+ it "should set a field from a constant in the blueprint" do
65
+ @post.title.should == "An Example Post"
66
+ end
67
+
68
+ it "should set a field from a block in the blueprint" do
69
+ @post.body.should == "The quick brown fox."
70
+ end
71
+
72
+ it "should save the object" do
73
+ @post.should be_saved
74
+ end
75
+
76
+ # There doesn't seem to be a way to do this with DataMapper.
77
+ #We can reload but not test that it's been done(without hacking at least)
78
+ # I'll leave it pending
79
+ it "should reload the object" #do
80
+ # @post.should be_reloaded
81
+ # end
82
+ end
83
+
84
+ describe "make_unsaved method" do
85
+ before do
86
+ @comment = Comment.make_unsaved
87
+ end
88
+
89
+ it "should not save the object" do
90
+ @comment.should_not be_saved
91
+ end
92
+
93
+ # Pending till I work out how to do this with DataMapper
94
+ it "should not reload the object" #do
95
+ # @comment.should_not be_reloaded
96
+ # end
97
+
98
+ it "should not save associated objects" do
99
+ @comment.post.should_not be_saved
100
+ end
101
+
102
+ # Pending till I work out how to do this with DataMapper
103
+ it "should not reload associated objects" #do
104
+ # @comment.post.should_not be_reloaded
105
+ # end
106
+ end
107
+
108
+ it "should override a field from the blueprint with a parameter" do
109
+ post = Post.make(:title => "A Different Title")
110
+ post.title.should == "A Different Title"
111
+ end
112
+
113
+ it "should create an associated object for a field with no arguments in the blueprint" do
114
+ comment = Comment.make
115
+ comment.post.should_not be_nil
116
+ end
117
+
118
+ it "should allow passing a block to make" do
119
+ comments = nil
120
+ post = Post.make do |post|
121
+ comments = (1..3).map { Comment.make(:post => post) }
122
+ end
123
+ post.should be_an_instance_of(Post)
124
+ comments.should_not be_nil
125
+ comments.each {|comment| comment.post.should == post }
126
+ end
127
+
128
+ it "should not nerf make within a block passed to make_unsaved" do
129
+ comment = nil
130
+ post = Post.make_unsaved do |post|
131
+ comment = Comment.make(:post => post)
132
+ end
133
+ post.should_not be_saved
134
+ comment.should be_saved
135
+ comment.post.should == post
136
+ end
137
+ end
data/spec/helpers.rb ADDED
@@ -0,0 +1,46 @@
1
+ class String
2
+ # Ripped from Rails Inflectors
3
+ def camelize(first_letter_in_uppercase = true)
4
+ lower_case_and_underscored_word = self
5
+ if first_letter_in_uppercase
6
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
7
+ else
8
+ lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
9
+ end
10
+ end
11
+
12
+ # Ripped from Rails Inflectors
13
+ def constantize
14
+ camel_cased_word = self
15
+ names = camel_cased_word.split('::')
16
+ names.shift if names.empty? || names.first.empty?
17
+
18
+ constant = Object
19
+ names.each do |name|
20
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
21
+ end
22
+ constant
23
+ end
24
+ end
25
+ module CustomMatchers
26
+ class DataMapperBeSaved
27
+ def initialize(expected)
28
+ @expected = expected
29
+ end
30
+
31
+ def matches?(target)
32
+ @target = target
33
+ p @target
34
+ p @expected
35
+ #satisfy expectation here
36
+ end
37
+
38
+ def failure_message
39
+ "expected #{@expected} but got #{@target}"
40
+ end
41
+
42
+ def negative_failure_message
43
+ "expected #{@expected} to not be #{@target}"
44
+ end
45
+ end
46
+ end
data/spec/sham_spec.rb ADDED
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ Sham.random { rand }
4
+ Sham.half_index {|index| index/2 }
5
+ Sham.coin_toss(:unique => false) {|index| index % 2 == 1 ? 'heads' : 'tails' }
6
+ Sham.limited {|index| index%10 }
7
+ Sham.index {|index| index }
8
+ Sham.name {|index| index }
9
+
10
+ describe Sham do
11
+ it "should ensure generated values are unique" do
12
+ values = (1..10).map { Sham.half_index }
13
+ values.should == (0..9).to_a
14
+ end
15
+
16
+ it "should generate non-unique values when asked" do
17
+ values = (1..4).map { Sham.coin_toss }
18
+ values.should == ['heads', 'tails', 'heads', 'tails']
19
+ end
20
+
21
+ it "should generate more than a dozen values" do
22
+ values = (1..25).map { Sham.index }
23
+ values.should == (1..25).to_a
24
+ end
25
+
26
+ it "should generate the same sequence of values after a reset" do
27
+ values1 = (1..10).map { Sham.random }
28
+ Sham.reset
29
+ values2 = (1..10).map { Sham.random }
30
+ values2.should == values1
31
+ end
32
+
33
+ it "should die when it runs out of unique values" do
34
+ lambda {
35
+ (1..100).map { Sham.limited }
36
+ }.should raise_error(RuntimeError)
37
+ end
38
+
39
+ it "should allow over-riding the name method" do
40
+ Sham.name.should == 1
41
+ end
42
+ end
data/spec/spec.opts ADDED
File without changes
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+ gem 'rspec', '>=1.1.3'
3
+ require 'spec'
4
+ require 'pathname'
5
+ require Pathname(__FILE__).dirname.expand_path.parent + 'lib/dm-machinist'
6
+ require Pathname(__FILE__).dirname.expand_path / 'helpers'
7
+
8
+ Spec::Runner.configure do |config|
9
+ config.include(CustomMatchers)
10
+ end
11
+
12
+
13
+ def load_driver(name, default_uri)
14
+ return false if ENV['ADAPTER'] != name.to_s
15
+
16
+ lib = "do_#{name}"
17
+
18
+ begin
19
+ gem lib, '>=0.9.5'
20
+ require lib
21
+ DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
22
+ DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
23
+ true
24
+ rescue Gem::LoadError => e
25
+ warn "Could not load #{lib}: #{e}"
26
+ false
27
+ end
28
+ end
29
+
30
+ ENV['ADAPTER'] ||= 'sqlite3'
31
+
32
+ HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
33
+
34
+ require "dm-types"
35
+ require "dm-aggregates"
36
+
37
+ # Create models
38
+ require Pathname(__FILE__).dirname.expand_path / 'data' / 'comment'
39
+ require Pathname(__FILE__).dirname.expand_path / 'data' / 'post'
40
+
41
+ # Migrate to sqlite3 in memory
42
+ DataMapper.auto_migrate!
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snepo-dm-machinist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Cameron Barrie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-27 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: dm-core
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: DataMapper plugin for machinist
25
+ email: cameron@snepo
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README
32
+ - LICENSE
33
+ - TODO
34
+ files:
35
+ - lib/dm-machinist/machinist.rb
36
+ - lib/dm-machinist/sham.rb
37
+ - lib/dm-machinist.rb
38
+ - spec/data/comment.rb
39
+ - spec/data/post.rb
40
+ - spec/dm_machinist_spec.rb
41
+ - spec/helpers.rb
42
+ - spec/sham_spec.rb
43
+ - spec/spec_helper.rb
44
+ - spec/spec.opts
45
+ - Rakefile
46
+ - README
47
+ - LICENSE
48
+ - TODO
49
+ has_rdoc: true
50
+ homepage: http://github.com/snepo/dm-machinist/tree/master
51
+ post_install_message:
52
+ rdoc_options: []
53
+
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.2.0
72
+ signing_key:
73
+ specification_version: 2
74
+ summary: DataMapper plugin for machinist
75
+ test_files: []
76
+