machinist 1.0.6 → 2.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/.gitignore +7 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +47 -0
- data/MIT-LICENSE +2 -1
- data/README.markdown +182 -204
- data/Rakefile +22 -29
- data/VERSION +1 -1
- data/lib/generators/machinist/install/USAGE +2 -0
- data/lib/generators/machinist/install/install_generator.rb +46 -0
- data/lib/generators/machinist/install/templates/blueprints.rb +9 -0
- data/lib/generators/machinist/install/templates/machinist.rb.erb +7 -0
- data/lib/generators/machinist/model/model_generator.rb +13 -0
- data/lib/machinist/active_record/blueprint.rb +16 -0
- data/lib/machinist/active_record/lathe.rb +24 -0
- data/lib/machinist/active_record.rb +8 -93
- data/lib/machinist/blueprint.rb +89 -0
- data/lib/machinist/exceptions.rb +32 -0
- data/lib/machinist/lathe.rb +68 -0
- data/lib/machinist/machinable.rb +95 -0
- data/lib/machinist/version.rb +3 -0
- data/lib/machinist.rb +4 -109
- data/machinist.gemspec +20 -65
- data/spec/active_record_spec.rb +85 -171
- data/spec/blueprint_spec.rb +76 -0
- data/spec/exceptions_spec.rb +20 -0
- data/spec/inheritance_spec.rb +104 -0
- data/spec/machinable_spec.rb +101 -0
- data/spec/spec_helper.rb +4 -6
- data/spec/support/active_record_environment.rb +65 -0
- metadata +125 -37
- data/.autotest +0 -7
- data/FAQ.markdown +0 -18
- data/init.rb +0 -2
- data/lib/machinist/blueprints.rb +0 -25
- data/lib/machinist/data_mapper.rb +0 -83
- data/lib/machinist/object.rb +0 -30
- data/lib/machinist/sequel.rb +0 -62
- data/lib/sham.rb +0 -77
- data/spec/data_mapper_spec.rb +0 -134
- data/spec/db/.gitignore +0 -1
- data/spec/db/schema.rb +0 -20
- data/spec/log/.gitignore +0 -1
- data/spec/machinist_spec.rb +0 -190
- data/spec/sequel_spec.rb +0 -146
- data/spec/sham_spec.rb +0 -95
@@ -0,0 +1,46 @@
|
|
1
|
+
module Machinist
|
2
|
+
module Generators #:nodoc:
|
3
|
+
class InstallGenerator < Rails::Generators::Base #:nodoc:
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
|
6
|
+
class_option :test_framework, :type => :string, :aliases => "-t", :desc => "Test framework to use Machinist with"
|
7
|
+
class_option :cucumber, :type => :boolean, :desc => "Set up access to Machinist from Cucumber"
|
8
|
+
|
9
|
+
def blueprints_file
|
10
|
+
if rspec?
|
11
|
+
copy_file "blueprints.rb", "spec/support/blueprints.rb"
|
12
|
+
else
|
13
|
+
copy_file "blueprints.rb", "test/blueprints.rb"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_helper
|
18
|
+
if test_unit?
|
19
|
+
inject_into_file("test/test_helper.rb", :after => "require 'rails/test_help'\n") do
|
20
|
+
"require File.expand_path(File.dirname(__FILE__) + '/blueprints')\n"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def cucumber_support
|
26
|
+
if cucumber?
|
27
|
+
template "machinist.rb.erb", "features/support/machinist.rb"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def rspec?
|
34
|
+
options[:test_framework].to_sym == :rspec
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_unit?
|
38
|
+
options[:test_framework].to_sym == :test_unit
|
39
|
+
end
|
40
|
+
|
41
|
+
def cucumber?
|
42
|
+
options[:cucumber]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Machinist
|
2
|
+
module Generators #:nodoc:
|
3
|
+
class ModelGenerator < Rails::Generators::NamedBase #:nodoc:
|
4
|
+
argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
|
5
|
+
|
6
|
+
def create_blueprint
|
7
|
+
append_file "spec/support/blueprints.rb", "\n#{class_name}.blueprint do\n # Attributes here\nend\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Machinist::ActiveRecord
|
2
|
+
class Blueprint < Machinist::Blueprint
|
3
|
+
|
4
|
+
# Make and save an object.
|
5
|
+
def make!(attributes = {})
|
6
|
+
object = make(attributes)
|
7
|
+
object.save!
|
8
|
+
object.reload
|
9
|
+
end
|
10
|
+
|
11
|
+
def lathe_class #:nodoc:
|
12
|
+
Machinist::ActiveRecord::Lathe
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Machinist::ActiveRecord
|
2
|
+
|
3
|
+
class Lathe < Machinist::Lathe
|
4
|
+
|
5
|
+
def make_one_value(attribute, args) #:nodoc:
|
6
|
+
if block_given?
|
7
|
+
raise_argument_error(attribute) unless args.empty?
|
8
|
+
yield
|
9
|
+
else
|
10
|
+
make_association(attribute, args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def make_association(attribute, args) #:nodoc:
|
15
|
+
association = @klass.reflect_on_association(attribute)
|
16
|
+
if association
|
17
|
+
association.klass.make(*args)
|
18
|
+
else
|
19
|
+
raise_argument_error(attribute)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -1,99 +1,14 @@
|
|
1
|
-
require 'machinist'
|
2
|
-
require 'machinist/blueprints'
|
3
1
|
require 'active_record'
|
2
|
+
require 'machinist'
|
3
|
+
require 'machinist/active_record/blueprint'
|
4
|
+
require 'machinist/active_record/lathe'
|
4
5
|
|
5
|
-
module
|
6
|
-
|
7
|
-
|
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 && !value.nil?
|
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
|
-
object = Machinist.with_save_nerfed { make(*args) }
|
63
|
-
yield object if block_given?
|
64
|
-
object
|
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
|
6
|
+
module ActiveRecord #:nodoc:
|
7
|
+
class Base #:nodoc:
|
8
|
+
extend Machinist::Machinable
|
83
9
|
|
84
|
-
def
|
85
|
-
|
86
|
-
Machinist::ActiveRecordAdapter.assigned_attributes_without_associations(lathe)
|
10
|
+
def self.blueprint_class
|
11
|
+
Machinist::ActiveRecord::Blueprint
|
87
12
|
end
|
88
13
|
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
14
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Machinist
|
2
|
+
|
3
|
+
# A Blueprint defines a method of constructing objects of a particular class.
|
4
|
+
class Blueprint
|
5
|
+
|
6
|
+
# Construct a blueprint for the given +klass+.
|
7
|
+
#
|
8
|
+
# Pass in the +:parent+ option to define a parent blueprint to apply after
|
9
|
+
# this one. You can supply another blueprint, or a class in which to look
|
10
|
+
# for a blueprint. In the latter case, make will walk up the superclass
|
11
|
+
# chain looking for blueprints to apply.
|
12
|
+
def initialize(klass, options = {}, &block)
|
13
|
+
@klass = klass
|
14
|
+
@parent = options[:parent]
|
15
|
+
@block = block
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :klass, :parent, :block
|
19
|
+
|
20
|
+
# Generate an object from this blueprint.
|
21
|
+
#
|
22
|
+
# Pass in attributes to override values defined in the blueprint.
|
23
|
+
def make(attributes = {})
|
24
|
+
lathe = lathe_class.new(@klass, new_serial_number, attributes)
|
25
|
+
|
26
|
+
lathe.instance_eval(&@block)
|
27
|
+
each_ancestor {|blueprint| lathe.instance_eval(&blueprint.block) }
|
28
|
+
|
29
|
+
lathe.object
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the Lathe class used to make objects for this blueprint.
|
33
|
+
#
|
34
|
+
# Subclasses can override this to substitute a custom lathe class.
|
35
|
+
def lathe_class
|
36
|
+
Lathe
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the parent blueprint for this blueprint.
|
40
|
+
def parent_blueprint
|
41
|
+
case @parent
|
42
|
+
when nil
|
43
|
+
nil
|
44
|
+
when Blueprint
|
45
|
+
# @parent references the parent blueprint directly.
|
46
|
+
@parent
|
47
|
+
else
|
48
|
+
# @parent is a class in which we should look for a blueprint.
|
49
|
+
find_blueprint_in_superclass_chain(@parent)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Yields the parent blueprint, its parent blueprint, etc.
|
54
|
+
def each_ancestor
|
55
|
+
ancestor = parent_blueprint
|
56
|
+
while ancestor
|
57
|
+
yield ancestor
|
58
|
+
ancestor = ancestor.parent_blueprint
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
def new_serial_number #:nodoc:
|
65
|
+
parent_blueprint = self.parent_blueprint # Cache this for speed.
|
66
|
+
if parent_blueprint
|
67
|
+
parent_blueprint.new_serial_number
|
68
|
+
else
|
69
|
+
@serial_number ||= 0
|
70
|
+
@serial_number += 1
|
71
|
+
sprintf("%04d", @serial_number)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def find_blueprint_in_superclass_chain(klass)
|
78
|
+
until has_blueprint?(klass) || klass.nil?
|
79
|
+
klass = klass.superclass
|
80
|
+
end
|
81
|
+
klass && klass.blueprint
|
82
|
+
end
|
83
|
+
|
84
|
+
def has_blueprint?(klass)
|
85
|
+
klass.respond_to?(:blueprint) && !klass.blueprint.nil?
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Machinist
|
2
|
+
|
3
|
+
# Raised when make! is called on a class whose blueprints don't support
|
4
|
+
# saving.
|
5
|
+
class BlueprintCantSaveError < RuntimeError
|
6
|
+
attr_reader :blueprint
|
7
|
+
|
8
|
+
def initialize(blueprint)
|
9
|
+
@blueprint = blueprint
|
10
|
+
end
|
11
|
+
|
12
|
+
def message
|
13
|
+
"make! is not supported by blueprints for class #{@blueprint.klass.name}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when calling make on a class with no corresponding blueprint
|
18
|
+
# defined.
|
19
|
+
class NoBlueprintError < RuntimeError
|
20
|
+
attr_reader :klass, :name
|
21
|
+
|
22
|
+
def initialize(klass, name)
|
23
|
+
@klass = klass
|
24
|
+
@name = name
|
25
|
+
end
|
26
|
+
|
27
|
+
def message
|
28
|
+
"No #{@name} blueprint defined for class #{@klass.name}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Machinist
|
2
|
+
|
3
|
+
# When you make an object, the blueprint for that object is instance-evaled
|
4
|
+
# against a Lathe.
|
5
|
+
#
|
6
|
+
# The Lathe implements all the methods that are available to the blueprint,
|
7
|
+
# including method_missing to let the blueprint define attributes.
|
8
|
+
class Lathe
|
9
|
+
|
10
|
+
def initialize(klass, serial_number, attributes = {})
|
11
|
+
@klass = klass
|
12
|
+
@serial_number = serial_number
|
13
|
+
@assigned_attributes = {}
|
14
|
+
|
15
|
+
@object = @klass.new
|
16
|
+
attributes.each {|key, value| assign_attribute(key, value) }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns a unique serial number for the object under construction.
|
20
|
+
def sn
|
21
|
+
@serial_number
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the object under construction.
|
25
|
+
attr_reader :object
|
26
|
+
|
27
|
+
def method_missing(attribute, *args, &block) #:nodoc:
|
28
|
+
unless attribute_assigned?(attribute)
|
29
|
+
assign_attribute(attribute, make_attribute(attribute, args, &block))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Undef a couple of methods that are common ActiveRecord attributes.
|
34
|
+
# (Both of these are deprecated in Ruby 1.8 anyway.)
|
35
|
+
undef_method :id if respond_to?(:id)
|
36
|
+
undef_method :type if respond_to?(:type)
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def make_attribute(attribute, args, &block) #:nodoc:
|
41
|
+
count = args.shift if args.first.is_a?(Fixnum)
|
42
|
+
if count
|
43
|
+
Array.new(count) { make_one_value(attribute, args, &block) }
|
44
|
+
else
|
45
|
+
make_one_value(attribute, args, &block)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def make_one_value(attribute, args) #:nodoc:
|
50
|
+
raise_argument_error(attribute) unless args.empty?
|
51
|
+
yield
|
52
|
+
end
|
53
|
+
|
54
|
+
def assign_attribute(key, value) #:nodoc:
|
55
|
+
@assigned_attributes[key.to_sym] = value
|
56
|
+
@object.send("#{key}=", value)
|
57
|
+
end
|
58
|
+
|
59
|
+
def attribute_assigned?(key) #:nodoc:
|
60
|
+
@assigned_attributes.has_key?(key.to_sym)
|
61
|
+
end
|
62
|
+
|
63
|
+
def raise_argument_error(attribute) #:nodoc:
|
64
|
+
raise ArgumentError.new("Invalid arguments to attribute #{attribute} in blueprint")
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Machinist
|
2
|
+
|
3
|
+
# Extend classes with this module to define the blueprint and make methods.
|
4
|
+
module Machinable
|
5
|
+
# Define a blueprint with the given name for this class.
|
6
|
+
#
|
7
|
+
# e.g.
|
8
|
+
# Post.blueprint do
|
9
|
+
# title { "A Post" }
|
10
|
+
# body { "Lorem ipsum..." }
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# If you provide the +name+ argument, a named blueprint will be created.
|
14
|
+
# See the +blueprint_name+ argument to the make method.
|
15
|
+
def blueprint(name = :master, &block)
|
16
|
+
@blueprints ||= {}
|
17
|
+
if block_given?
|
18
|
+
parent = (name == :master ? superclass : self) # Where should we look for the parent blueprint?
|
19
|
+
@blueprints[name] = blueprint_class.new(self, :parent => parent, &block)
|
20
|
+
end
|
21
|
+
@blueprints[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Construct an object from a blueprint.
|
25
|
+
#
|
26
|
+
# :call-seq:
|
27
|
+
# make([count], [blueprint_name], [attributes = {}])
|
28
|
+
#
|
29
|
+
# [+count+]
|
30
|
+
# The number of objects to construct. If +count+ is provided, make
|
31
|
+
# returns an array of objects rather than a single object.
|
32
|
+
# [+blueprint_name+]
|
33
|
+
# Construct the object from the named blueprint, rather than the master
|
34
|
+
# blueprint.
|
35
|
+
# [+attributes+]
|
36
|
+
# Override the attributes from the blueprint with values from this hash.
|
37
|
+
def make(*args)
|
38
|
+
decode_args_to_make(*args) do |blueprint, attributes|
|
39
|
+
blueprint.make(attributes)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Construct and save an object from a blueprint, if the class allows saving.
|
44
|
+
#
|
45
|
+
# :call-seq:
|
46
|
+
# make!([count], [blueprint_name], [attributes = {}])
|
47
|
+
#
|
48
|
+
# Arguments are the same as for make.
|
49
|
+
def make!(*args)
|
50
|
+
decode_args_to_make(*args) do |blueprint, attributes|
|
51
|
+
raise BlueprintCantSaveError.new(blueprint) unless blueprint.respond_to?(:make!)
|
52
|
+
blueprint.make!(attributes)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Remove all blueprints defined on this class.
|
57
|
+
def clear_blueprints!
|
58
|
+
@blueprints = {}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Classes that include Machinable can override this method if they want to
|
62
|
+
# use a custom blueprint class when constructing blueprints.
|
63
|
+
#
|
64
|
+
# The default is Machinist::Blueprint.
|
65
|
+
def blueprint_class
|
66
|
+
Machinist::Blueprint
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Parses the arguments to make.
|
72
|
+
#
|
73
|
+
# Yields a blueprint and an attributes hash to the block, which should
|
74
|
+
# construct an object from them. The block may be called multiple times to
|
75
|
+
# construct multiple objects.
|
76
|
+
def decode_args_to_make(*args) #:nodoc:
|
77
|
+
shift_arg = lambda {|klass| args.shift if args.first.is_a?(klass) }
|
78
|
+
count = shift_arg[Fixnum]
|
79
|
+
name = shift_arg[Symbol] || :master
|
80
|
+
attributes = shift_arg[Hash] || {}
|
81
|
+
raise ArgumentError.new("Couldn't understand arguments") unless args.empty?
|
82
|
+
|
83
|
+
@blueprints ||= {}
|
84
|
+
blueprint = @blueprints[name]
|
85
|
+
raise NoBlueprintError.new(self, name) unless blueprint
|
86
|
+
|
87
|
+
if count.nil?
|
88
|
+
yield(blueprint, attributes)
|
89
|
+
else
|
90
|
+
Array.new(count) { yield(blueprint, attributes) }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
data/lib/machinist.rb
CHANGED
@@ -1,110 +1,5 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
1
|
+
require 'machinist/blueprint'
|
2
|
+
require 'machinist/exceptions'
|
3
|
+
require 'machinist/lathe'
|
4
|
+
require 'machinist/machinable'
|
4
5
|
|
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
|
-
|
14
|
-
raise "No blueprint for class #{object.class}" if blueprint.nil?
|
15
|
-
|
16
|
-
lathe = self.new(adapter, object, attributes)
|
17
|
-
lathe.instance_eval(&named_blueprint) if named_blueprint
|
18
|
-
klass = object.class
|
19
|
-
while klass
|
20
|
-
lathe.instance_eval(&klass.blueprint) if klass.respond_to?(:blueprint) && klass.blueprint
|
21
|
-
klass = klass.superclass
|
22
|
-
end
|
23
|
-
lathe
|
24
|
-
end
|
25
|
-
|
26
|
-
def initialize(adapter, object, attributes = {})
|
27
|
-
@adapter = adapter
|
28
|
-
@object = object
|
29
|
-
attributes.each {|key, value| assign_attribute(key, value) }
|
30
|
-
end
|
31
|
-
|
32
|
-
def object
|
33
|
-
yield @object if block_given?
|
34
|
-
@object
|
35
|
-
end
|
36
|
-
|
37
|
-
def method_missing(symbol, *args, &block)
|
38
|
-
if attribute_assigned?(symbol)
|
39
|
-
# If we've already assigned the attribute, return that.
|
40
|
-
@object.send(symbol)
|
41
|
-
elsif @adapter.has_association?(@object, symbol) && !nil_or_empty?(@object.send(symbol))
|
42
|
-
# If the attribute is an association and is already assigned, return that.
|
43
|
-
@object.send(symbol)
|
44
|
-
else
|
45
|
-
# Otherwise generate a value and assign it.
|
46
|
-
assign_attribute(symbol, generate_attribute_value(symbol, *args, &block))
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def assigned_attributes
|
51
|
-
@assigned_attributes ||= {}
|
52
|
-
end
|
53
|
-
|
54
|
-
# Undef a couple of methods that are common ActiveRecord attributes.
|
55
|
-
# (Both of these are deprecated in Ruby 1.8 anyway.)
|
56
|
-
undef_method :id if respond_to?(:id)
|
57
|
-
undef_method :type if respond_to?(:type)
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def nil_or_empty?(object)
|
62
|
-
object.respond_to?(:empty?) ? object.empty? : object.nil?
|
63
|
-
end
|
64
|
-
|
65
|
-
def assign_attribute(key, value)
|
66
|
-
assigned_attributes[key.to_sym] = value
|
67
|
-
@object.send("#{key}=", value)
|
68
|
-
end
|
69
|
-
|
70
|
-
def attribute_assigned?(key)
|
71
|
-
assigned_attributes.has_key?(key.to_sym)
|
72
|
-
end
|
73
|
-
|
74
|
-
def generate_attribute_value(attribute, *args)
|
75
|
-
if block_given?
|
76
|
-
# If we've got a block, use that to generate the value.
|
77
|
-
yield
|
78
|
-
else
|
79
|
-
# Otherwise, look for an association or a sham.
|
80
|
-
if @adapter.has_association?(object, attribute)
|
81
|
-
@adapter.class_for_association(object, attribute).make(args.first || {})
|
82
|
-
elsif args.empty?
|
83
|
-
Sham.send(attribute)
|
84
|
-
else
|
85
|
-
# If we've got a constant, just use that.
|
86
|
-
args.first
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
# This sets a flag that stops make from saving objects, so
|
94
|
-
# that calls to make from within a blueprint don't create
|
95
|
-
# anything inside make_unsaved.
|
96
|
-
def self.with_save_nerfed
|
97
|
-
begin
|
98
|
-
@@nerfed = true
|
99
|
-
yield
|
100
|
-
ensure
|
101
|
-
@@nerfed = false
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
@@nerfed = false
|
106
|
-
def self.nerfed?
|
107
|
-
@@nerfed
|
108
|
-
end
|
109
|
-
|
110
|
-
end
|