pk-machinist 1.0.3

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,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
+