pk-machinist 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ require 'sham'
2
+
3
+ module Machinist
4
+
5
+ # A Lathe is used to execute the blueprint and construct an object.
6
+ #
7
+ # The blueprint is instance_eval'd against the Lathe.
8
+ class Lathe
9
+ def self.run(adapter, object, *args)
10
+ blueprint = object.class.blueprint
11
+ named_blueprint = object.class.blueprint(args.shift) if args.first.is_a?(Symbol)
12
+ attributes = args.pop || {}
13
+ raise "No blueprint for class #{object.class}" if blueprint.nil?
14
+ returning self.new(adapter, object, attributes) do |lathe|
15
+ lathe.instance_eval(&named_blueprint) if named_blueprint
16
+ lathe.instance_eval(&blueprint)
17
+ end
18
+ end
19
+
20
+ def initialize(adapter, object, attributes = {})
21
+ @adapter = adapter
22
+ @object = object
23
+ attributes.each {|key, value| assign_attribute(key, value) }
24
+ end
25
+
26
+ def object
27
+ yield @object if block_given?
28
+ @object
29
+ end
30
+
31
+ def method_missing(symbol, *args, &block)
32
+ if attribute_assigned?(symbol)
33
+ # If we've already assigned the attribute, return that.
34
+ @object.send(symbol)
35
+ elsif @adapter.has_association?(@object, symbol) && !@object.send(symbol).nil?
36
+ # If the attribute is an association and is already assigned, return that.
37
+ @object.send(symbol)
38
+ else
39
+ # Otherwise generate a value and assign it.
40
+ assign_attribute(symbol, generate_attribute_value(symbol, *args, &block))
41
+ end
42
+ end
43
+
44
+ def assigned_attributes
45
+ @assigned_attributes ||= {}
46
+ end
47
+
48
+ # Undef a couple of methods that are common ActiveRecord attributes.
49
+ # (Both of these are deprecated in Ruby 1.8 anyway.)
50
+ undef_method :id if respond_to?(:id)
51
+ undef_method :type if respond_to?(:type)
52
+
53
+ private
54
+
55
+ def assign_attribute(key, value)
56
+ assigned_attributes[key.to_sym] = value
57
+ @object.send("#{key}=", value)
58
+ end
59
+
60
+ def attribute_assigned?(key)
61
+ assigned_attributes.has_key?(key.to_sym)
62
+ end
63
+
64
+ def generate_attribute_value(attribute, *args)
65
+ if block_given?
66
+ # If we've got a block, use that to generate the value.
67
+ yield
68
+ elsif !args.empty?
69
+ # If we've got a constant, just use that.
70
+ args.first
71
+ else
72
+ # Otherwise, look for an association or a sham.
73
+ if @adapter.has_association?(object, attribute)
74
+ @adapter.class_for_association(object, attribute).make(args.first || {})
75
+ else
76
+ Sham.send(attribute)
77
+ end
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ # This sets a flag that stops make from saving objects, so
84
+ # that calls to make from within a blueprint don't create
85
+ # anything inside make_unsaved.
86
+ def self.with_save_nerfed
87
+ begin
88
+ @@nerfed = true
89
+ yield
90
+ ensure
91
+ @@nerfed = false
92
+ end
93
+ end
94
+
95
+ @@nerfed = false
96
+ def self.nerfed?
97
+ @@nerfed
98
+ end
99
+
100
+ end
@@ -0,0 +1,99 @@
1
+ require 'machinist'
2
+ require 'machinist/blueprints'
3
+ require 'active_record'
4
+
5
+ module Machinist
6
+
7
+ class ActiveRecordAdapter
8
+
9
+ def self.has_association?(object, attribute)
10
+ object.class.reflect_on_association(attribute)
11
+ end
12
+
13
+ def self.class_for_association(object, attribute)
14
+ association = object.class.reflect_on_association(attribute)
15
+ association && association.klass
16
+ end
17
+
18
+ # This method takes care of converting any associated objects,
19
+ # in the hash returned by Lathe#assigned_attributes, into their
20
+ # object ids.
21
+ #
22
+ # For example, let's say we have blueprints like this:
23
+ #
24
+ # Post.blueprint { }
25
+ # Comment.blueprint { post }
26
+ #
27
+ # Lathe#assigned_attributes will return { :post => ... }, but
28
+ # we want to pass { :post_id => 1 } to a controller.
29
+ #
30
+ # This method takes care of cleaning this up.
31
+ def self.assigned_attributes_without_associations(lathe)
32
+ attributes = {}
33
+ lathe.assigned_attributes.each_pair do |attribute, value|
34
+ association = lathe.object.class.reflect_on_association(attribute)
35
+ if association && association.macro == :belongs_to
36
+ attributes[association.primary_key_name.to_sym] = value.id
37
+ else
38
+ attributes[attribute] = value
39
+ end
40
+ end
41
+ attributes
42
+ end
43
+
44
+ end
45
+
46
+ module ActiveRecordExtensions
47
+ def self.included(base)
48
+ base.extend(ClassMethods)
49
+ end
50
+
51
+ module ClassMethods
52
+ def make(*args, &block)
53
+ lathe = Lathe.run(Machinist::ActiveRecordAdapter, self.new, *args)
54
+ unless Machinist.nerfed?
55
+ lathe.object.save!
56
+ lathe.object.reload
57
+ end
58
+ lathe.object(&block)
59
+ end
60
+
61
+ def make_unsaved(*args)
62
+ returning(Machinist.with_save_nerfed { make(*args) }) do |object|
63
+ yield object if block_given?
64
+ end
65
+ end
66
+
67
+ def plan(*args)
68
+ lathe = Lathe.run(Machinist::ActiveRecordAdapter, self.new, *args)
69
+ Machinist::ActiveRecordAdapter.assigned_attributes_without_associations(lathe)
70
+ end
71
+ end
72
+ end
73
+
74
+ module ActiveRecordHasManyExtensions
75
+ def make(*args, &block)
76
+ lathe = Lathe.run(Machinist::ActiveRecordAdapter, self.build, *args)
77
+ unless Machinist.nerfed?
78
+ lathe.object.save!
79
+ lathe.object.reload
80
+ end
81
+ lathe.object(&block)
82
+ end
83
+
84
+ def plan(*args)
85
+ lathe = Lathe.run(Machinist::ActiveRecordAdapter, self.build, *args)
86
+ Machinist::ActiveRecordAdapter.assigned_attributes_without_associations(lathe)
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ class ActiveRecord::Base
93
+ include Machinist::Blueprints
94
+ include Machinist::ActiveRecordExtensions
95
+ end
96
+
97
+ class ActiveRecord::Associations::HasManyAssociation
98
+ include Machinist::ActiveRecordHasManyExtensions
99
+ end
@@ -0,0 +1,25 @@
1
+ module Machinist
2
+ # Include this in a class to allow defining blueprints for that class.
3
+ module Blueprints
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def blueprint(name = :master, &blueprint)
10
+ @blueprints ||= {}
11
+ @blueprints[name] = blueprint if block_given?
12
+ @blueprints[name]
13
+ end
14
+
15
+ def named_blueprints
16
+ @blueprints.reject{|name,_| name == :master }.keys
17
+ end
18
+
19
+ def clear_blueprints!
20
+ @blueprints = {}
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,83 @@
1
+ require 'machinist'
2
+ require 'machinist/blueprints'
3
+ require 'dm-core'
4
+
5
+ module Machinist
6
+
7
+ class DataMapperAdapter
8
+ def self.has_association?(object, attribute)
9
+ object.class.relationships.has_key?(attribute)
10
+ end
11
+
12
+ def self.class_for_association(object, attribute)
13
+ association = object.class.relationships[attribute]
14
+ association && association.parent_model
15
+ end
16
+
17
+ def self.association_is_many_to_one?(association)
18
+ if defined?(DataMapper::Associations::ManyToOne::Relationship)
19
+ # We're using the next branch of DM
20
+ association.class == DataMapper::Associations::ManyToOne::Relationship
21
+ else
22
+ # We're using the 0.9 or less branch.
23
+ association.options[:max].nil?
24
+ end
25
+ end
26
+
27
+ # This method takes care of converting any associated objects,
28
+ # in the hash returned by Lathe#assigned_attributes, into their
29
+ # object ids.
30
+ #
31
+ # For example, let's say we have blueprints like this:
32
+ #
33
+ # Post.blueprint { }
34
+ # Comment.blueprint { post }
35
+ #
36
+ # Lathe#assigned_attributes will return { :post => ... }, but
37
+ # we want to pass { :post_id => 1 } to a controller.
38
+ #
39
+ # This method takes care of cleaning this up.
40
+ def self.assigned_attributes_without_associations(lathe)
41
+ attributes = {}
42
+ lathe.assigned_attributes.each_pair do |attribute, value|
43
+ association = lathe.object.class.relationships[attribute]
44
+ if association && association_is_many_to_one?(association)
45
+ # DataMapper child_key can have more than one property, but I'm not
46
+ # sure in what circumstances this would be the case. I'm assuming
47
+ # here that there's only one property.
48
+ key = association.child_key.map(&:field).first.to_sym
49
+ attributes[key] = value.id
50
+ else
51
+ attributes[attribute] = value
52
+ end
53
+ end
54
+ attributes
55
+ end
56
+ end
57
+
58
+ module DataMapperExtensions
59
+ def make(*args, &block)
60
+ lathe = Lathe.run(Machinist::DataMapperAdapter, self.new, *args)
61
+ unless Machinist.nerfed?
62
+ lathe.object.save || raise("Save failed")
63
+ lathe.object.reload
64
+ end
65
+ lathe.object(&block)
66
+ end
67
+
68
+ def make_unsaved(*args)
69
+ returning(Machinist.with_save_nerfed { make(*args) }) do |object|
70
+ yield object if block_given?
71
+ end
72
+ end
73
+
74
+ def plan(*args)
75
+ lathe = Lathe.run(Machinist::DataMapperAdapter, self.new, *args)
76
+ Machinist::DataMapperAdapter.assigned_attributes_without_associations(lathe)
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ DataMapper::Model.append_extensions(Machinist::Blueprints::ClassMethods)
83
+ DataMapper::Model.append_extensions(Machinist::DataMapperExtensions)
@@ -0,0 +1,30 @@
1
+ require 'machinist'
2
+ require 'machinist/blueprints'
3
+
4
+ module Machinist
5
+
6
+ module ObjectExtensions
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ def make(*args, &block)
13
+ lathe = Lathe.run(Machinist::ObjectAdapter, self.new, *args)
14
+ lathe.object(&block)
15
+ end
16
+ end
17
+ end
18
+
19
+ class ObjectAdapter
20
+ def self.has_association?(object, attribute)
21
+ false
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ class Object
28
+ include Machinist::Blueprints
29
+ include Machinist::ObjectExtensions
30
+ end
@@ -0,0 +1,81 @@
1
+ require 'machinist'
2
+ require 'machinist/blueprints'
3
+ require 'sequel'
4
+
5
+ module Machinist
6
+
7
+ class SequelAdapter
8
+
9
+ def self.has_association?(object, attribute)
10
+ object.class.associations.include? attribute
11
+ end
12
+
13
+ def self.class_for_association(object, attribute)
14
+ association = object.class.association_reflection(attribute)
15
+ association && Module.const_get(association[:class_name].to_sym)
16
+ end
17
+
18
+ # This method takes care of converting any associated objects,
19
+ # in the hash returned by Lathe#assigned_attributes, into their
20
+ # object ids.
21
+ #
22
+ # For example, let's say we have blueprints like this:
23
+ #
24
+ # Post.blueprint { }
25
+ # Comment.blueprint { post }
26
+ #
27
+ # Lathe#assigned_attributes will return { :post => ... }, but
28
+ # we want to pass { :post_id => 1 } to a controller.
29
+ #
30
+ # This method takes care of cleaning this up.
31
+ def self.assigned_attributes_without_associations(lathe)
32
+ attributes = {}
33
+ lathe.assigned_attributes.each_pair do |attribute, value|
34
+ association = lathe.object.class.association_reflection(attribute)
35
+ if association && association[:type] == :many_to_one
36
+ attributes[association[:key]] = value.id
37
+ else
38
+ attributes[attribute] = value
39
+ end
40
+ end
41
+ attributes
42
+ end
43
+ end
44
+
45
+ module SequelExtensions
46
+
47
+ def self.included(base)
48
+ base.extend(ClassMethods)
49
+ end
50
+
51
+ module ClassMethods
52
+
53
+ def make(*args, &block)
54
+ lathe = Lathe.run(Machinist::SequelAdapter, self.new, *args)
55
+ unless Machinist.nerfed?
56
+ lathe.object.save || raise("Save failed")
57
+ lathe.object.reload
58
+ end
59
+ lathe.object(&block)
60
+ end
61
+
62
+ def make_unsaved(*args)
63
+ returning(Machinist.with_save_nerfed { make(*args) }) do |object|
64
+ yield object if block_given?
65
+ end
66
+ end
67
+
68
+ def plan(*args)
69
+ lathe = Lathe.run(Machinist::SequelAdapter, self.new, *args)
70
+ Machinist::SequelAdapter.assigned_attributes_without_associations(lathe)
71
+ end
72
+
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ class Sequel::Model
79
+ include Machinist::Blueprints
80
+ include Machinist::SequelExtensions
81
+ end
@@ -0,0 +1,79 @@
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.clear
24
+ @@shams = {}
25
+ end
26
+
27
+ def self.reset(scope = :before_all)
28
+ @@shams.values.each { |sham| sham.reset(scope) }
29
+ end
30
+
31
+ def self.define(&block)
32
+ Sham.instance_eval(&block)
33
+ end
34
+
35
+ def initialize(name, options = {}, &block)
36
+ @name = name
37
+ @generator = block
38
+ @offset = 0
39
+ @unique = options.has_key?(:unique) ? options[:unique] : true
40
+ generate_values(12)
41
+ end
42
+
43
+ def reset(scope)
44
+ if scope == :before_all
45
+ @offset, @before_offset = 0, nil
46
+ elsif @before_offset
47
+ @offset = @before_offset
48
+ else
49
+ @before_offset = @offset
50
+ end
51
+ end
52
+
53
+ def fetch_value
54
+ # Generate more values if we need them.
55
+ if @offset >= @values.length
56
+ generate_values(2 * @values.length)
57
+ raise "Can't generate more unique values for Sham.#{@name}" if @offset >= @values.length
58
+ end
59
+ returning @values[@offset] do
60
+ @offset += 1
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def generate_values(count)
67
+ @values = seeded { (1..count).map(&@generator) }
68
+ @values.uniq! if @unique
69
+ end
70
+
71
+ def seeded
72
+ begin
73
+ srand(1)
74
+ yield
75
+ ensure
76
+ srand
77
+ end
78
+ end
79
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pk-machinist
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Pete Yandell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-16 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: pete@nothat.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/machinist.rb
26
+ - lib/sham.rb
27
+ - lib/machinist/blueprints.rb
28
+ - lib/machinist/object.rb
29
+ - lib/machinist/active_record.rb
30
+ - lib/machinist/data_mapper.rb
31
+ - lib/machinist/sequel.rb
32
+ has_rdoc: false
33
+ homepage: http://github.com/notahat/machinist
34
+ post_install_message:
35
+ rdoc_options: []
36
+
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: Fixtures aren't fun. Machinist is.
58
+ test_files: []
59
+