forked from notahat/machinist
/
machinist.rb
122 lines (103 loc) · 3.4 KB
/
machinist.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
require 'sham'
module Machinist
# A Lathe is used to execute the blueprint and construct an object.
#
# The blueprint is instance_eval'd against the Lathe.
class Lathe
def self.run(adapter, object, *args)
if args.first.is_a?(Symbol)
name = args.shift
named_blueprint = object.class.blueprint(name)
raise "No blueprint named '#{name}' defined for class #{object.class}" if named_blueprint.nil?
end
blueprint = object.class.blueprint
if blueprint.nil?
if named_blueprint
raise "Can't construct an object from a named blueprint without a default blueprint for class #{object.class}"
else
raise "No blueprint for class #{object.class}"
end
end
attributes = args.pop || {}
lathe = self.new(adapter, object, attributes)
lathe.instance_eval(&named_blueprint) if named_blueprint
klass = object.class
while klass
lathe.instance_eval(&klass.blueprint) if klass.respond_to?(:blueprint) && klass.blueprint
klass = klass.superclass
end
lathe
end
def initialize(adapter, object, attributes = {})
@adapter = adapter
@object = object
attributes.each {|key, value| assign_attribute(key, value) }
end
def object
yield @object if block_given?
@object
end
def method_missing(symbol, *args, &block)
if attribute_assigned?(symbol)
# If we've already assigned the attribute, return that.
@object.send(symbol)
elsif @adapter.has_association?(@object, symbol) && !nil_or_empty?(@object.send(symbol))
# If the attribute is an association and is already assigned, return that.
@object.send(symbol)
else
# Otherwise generate a value and assign it.
assign_attribute(symbol, generate_attribute_value(symbol, *args, &block))
end
end
def assigned_attributes
@assigned_attributes ||= {}
end
# Undef a couple of methods that are common ActiveRecord attributes.
# (Both of these are deprecated in Ruby 1.8 anyway.)
undef_method :id if respond_to?(:id)
undef_method :type if respond_to?(:type)
private
def nil_or_empty?(object)
object.respond_to?(:empty?) ? object.empty? : object.nil?
end
def assign_attribute(key, value)
assigned_attributes[key.to_sym] = value
@object.send("#{key}=", value)
end
def attribute_assigned?(key)
assigned_attributes.has_key?(key.to_sym)
end
def generate_attribute_value(attribute, *args)
if block_given?
# If we've got a block, use that to generate the value.
yield
else
# Otherwise, look for an association or a sham.
if @adapter.has_association?(object, attribute)
@adapter.class_for_association(object, attribute).make(args.first || {})
elsif args.empty?
Sham.send(attribute)
else
# If we've got a constant, just use that.
args.first
end
end
end
end
# This sets a flag that stops make from saving objects, so
# that calls to make from within a blueprint don't create
# anything inside make_unsaved.
def self.with_save_nerfed
prior_nerfed = @@nerfed
begin
@@nerfed = true
yield
ensure
@@nerfed = prior_nerfed
end
end
@@nerfed = false
def self.nerfed?
@@nerfed
end
end