object_momma 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +197 -0
- data/Rakefile +20 -0
- data/lib/object_momma/builder.rb +164 -0
- data/lib/object_momma/child.rb +126 -0
- data/lib/object_momma/class_attributes.rb +19 -0
- data/lib/object_momma/config.rb +28 -0
- data/lib/object_momma/module_methods.rb +80 -0
- data/lib/object_momma/version.rb +3 -0
- data/lib/object_momma.rb +21 -0
- data/object_momma.gemspec +19 -0
- data/spec/fixtures/blog_post_voting_classes.rb +135 -0
- data/spec/fixtures/comment.rb +8 -0
- data/spec/fixtures/fake_model.rb +90 -0
- data/spec/fixtures/post.rb +5 -0
- data/spec/fixtures/thing_builder.rb +18 -0
- data/spec/fixtures/user.rb +5 -0
- data/spec/fixtures/users.yml +3 -0
- data/spec/fixtures/vote.rb +16 -0
- data/spec/object_momma_spec.rb +223 -0
- data/tmp/.gitkeep +0 -0
- metadata +104 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 ntl
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
# ObjectMomma
|
2
|
+
|
3
|
+
An Object Mother implementation in ruby
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
ObjectMomma is a gem that implements the Object Mother pattern for managing test data. It shares a lot of goals with FactoryGirl, but represents a fundamentally different approach. See [this article by Martin Fowler](http://martinfowler.com/bliki/ObjectMother.html) for more information.
|
8
|
+
|
9
|
+
## Vs. Fixtures and Factories
|
10
|
+
|
11
|
+
The Object Mother pattern shares some benefits with fixtures -- namely, by assigning fixed names (I call them *child identifiers*) to the objects you define, you can mentally associate the properties of each object with those names. For instance, the user called "Joe Spammer" may be used pervasively throughout tests that cover comment moderation. You can even build larger scenarios that can be re-used over and over by your team.
|
12
|
+
|
13
|
+
However, unlike fixtures, instead of managing yml files that directly manipulate database state, you can build out your objects in ruby with all of your model behaviors available. In this way, you get some of the best benefits of both fixtures and traditional factories.
|
14
|
+
|
15
|
+
The biggest benefit of Object Mother over both fixtures and factories is that they are not tied to ActiveRecord, so you can actually use ObjectMomma to refactor your entire model structure if you need to. ObjectMomma's goal is to facilitate data set up for your acceptance tests, not unit tests, so there is no reason that you couldn't use ObjectMomma alongside FactoryGirl or fixtures. This approach is similar to using cucumber for acceptance tests and rspec or Test::Unit for your unit/controller tests.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Install the gem locally:
|
20
|
+
|
21
|
+
gem install object_momma
|
22
|
+
|
23
|
+
Or add it to your Gemfile:
|
24
|
+
|
25
|
+
gem 'object_momma'
|
26
|
+
|
27
|
+
and run `bundle install` from your shell.
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
### How your spec should look
|
32
|
+
|
33
|
+
`rspec/capybara`:
|
34
|
+
|
35
|
+
feature "Moderating comments" do
|
36
|
+
let(:post) do
|
37
|
+
ObjectMomma.post("Birds")
|
38
|
+
end
|
39
|
+
|
40
|
+
background do
|
41
|
+
ObjectMomma.spawn_comment("Joe Schmoe's Comment on the Post about Birds")
|
42
|
+
end
|
43
|
+
|
44
|
+
scenario "Up voting a comment" do
|
45
|
+
visit post_path(post)
|
46
|
+
# etc.
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
`Test::Unit`:
|
51
|
+
|
52
|
+
class CommentModeration < ActionController::IntegrationTest
|
53
|
+
def setup
|
54
|
+
@post = ObjectMomma.post("Birds")
|
55
|
+
ObjectMomma.spawn_post("Joe Schmoe's Comment on the Post About Birds")
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_upvoting_a_comment
|
59
|
+
visit post_path(post)
|
60
|
+
# etc.
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
### Using ObjectMomma
|
65
|
+
|
66
|
+
To create new objects, use methods that start with `ObjectMomma.spawn_*`, e.g. `ObjectMomma.spawn_user`:
|
67
|
+
|
68
|
+
=> user = ObjectMomma.spawn_user("Joe Schmoe")
|
69
|
+
-> <#User:0xdeadbeef>
|
70
|
+
|
71
|
+
In this case above, "Joe Schmoe" can be thought of as the *child identifier* for the particular child we're creating.
|
72
|
+
|
73
|
+
Some objects are composed of other objects, and they can actually nest those child identifiers neatly, e.g. `Joe Schmoe's Comment on the Post about Birds.` You can spawn them by either referring to their composite child identifier, or by a hash mapping the associated object names to their identifiers:
|
74
|
+
|
75
|
+
=> comment = ObjectMomma.spawn_comment("Joe Schmoe's Comment on the Post about Birds")
|
76
|
+
=> comment = ObjectMomma.spawn_comment(user: "Joe Schmoe", post: "Birds")
|
77
|
+
|
78
|
+
`ObjectMomma.spawn_*` will raise an `ObjectMomma::ObjectExists` exception if the object has already been spawned. To simply return the object if it does exist, omit the `spawn_` and invoke ObjectMomma thusly:
|
79
|
+
|
80
|
+
=> ObjectMomma.spawn("Joe Schmoe") # Works the first time
|
81
|
+
=> ObjectMomma.spawn("Joe Schmoe") # Now raises an ObjectExists exception
|
82
|
+
=> ObjectMomma.user("Joe Schmoe") # Works fine :)
|
83
|
+
|
84
|
+
If you want to raise an error if the object *doesn't* exist, then `ObjectMomma.find_*` will raise an `ObjectMomma::ObjectNotFound` exception in that case:
|
85
|
+
|
86
|
+
=> ObjectMomma.find("Joe Schmoe") # Raises ObjectNotFound exception
|
87
|
+
=> ObjectMomma.spawn("Joe Schmoe") # Works fine :)
|
88
|
+
=> ObjectMomma.find("Joe Schmoe") # Does not raise exception
|
89
|
+
|
90
|
+
You can also build a bunch of objects all at once with `ObjectMomma.spawn`:
|
91
|
+
|
92
|
+
=> ObjectMomma.spawn({
|
93
|
+
posts: ["Birds", "Middle Earth", "Sports"],
|
94
|
+
users: ["Joe Schmoe", "Scott Pilgrim"],
|
95
|
+
comment: "Billy Pilgrim's Comment on Post about Cooking"
|
96
|
+
})
|
97
|
+
|
98
|
+
The idea with the single call to `ObjectMomma.spawn` is to be able to encapsulate complicated data setup with a simple, semantic call.
|
99
|
+
|
100
|
+
### Teaching ObjectMomma how to build new types of objects
|
101
|
+
|
102
|
+
The examples above assumed that ObjectMomma knew how to build out the objects in question. To actually teach ObjectMomma how to build the objects, consider:
|
103
|
+
|
104
|
+
In `spec/object_momma/user.rb`:
|
105
|
+
|
106
|
+
class ObjectMomma::UserBuilder < ObjectMomma::Builder
|
107
|
+
def first_or_initialize
|
108
|
+
User.where(full_name: self.child_id)
|
109
|
+
end
|
110
|
+
|
111
|
+
def build!(user)
|
112
|
+
user.full_name = child.child_id
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
The method `#first_or_initialize` has the most important role of the builder: it grabs the object from the persistence layer if it exists, otherwise, it initializes a new object.
|
117
|
+
|
118
|
+
After grabbing an object from `#first_or_initialize`, ObjectMomma will invoke `#build!` if and only if the object hasn't yet been persisted. `#build!` will actually set up the data and persist the object itself.
|
119
|
+
|
120
|
+
#### Composed Builders
|
121
|
+
|
122
|
+
You can also implement builders that know how to compose objects from their associated objects:
|
123
|
+
|
124
|
+
class ObjectMomma::CommentBuilder < ObjectMomma::Builder
|
125
|
+
child_id { "#{author}'s Comment on #{post}" }
|
126
|
+
has_siblings :post, author: user
|
127
|
+
|
128
|
+
def first_or_initialize
|
129
|
+
Comment.where(user_id: self.author.id, post_id: self.post.id)
|
130
|
+
end
|
131
|
+
|
132
|
+
def build!(object)
|
133
|
+
if object.user.spammer?
|
134
|
+
object.text = "Check out teh cheap pillz from mycheappillz.com!!!"
|
135
|
+
elsif object.post.subject == :birds
|
136
|
+
# etc. …
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
It is important to note the distinction between `self` and `object` in these cases. `self` refers to the builder which has been hydrated with properties from the `child_id` block, whereas `object` refers to the child object itself.
|
142
|
+
|
143
|
+
### But I don't *want* to use ActiveRecord!
|
144
|
+
|
145
|
+
The only requirement ObjectMomma imposes on the objects that are spawned via your `first_or_initialize` methods is that they respond to `#persisted?` the way ActiveRecord objects do. The gem itself does not depend on ActiveRecord at all; feel free to use whatever persistence layer you want. Consider overriding `ObjectMomma::Builder.is_persisted?` if your objects don't respond to `#persisted?`.
|
146
|
+
|
147
|
+
Your builder's `first_or_initialize` method can return a Scope, as well. ObjectMomma will simply call `first_or_initialize` for you.
|
148
|
+
|
149
|
+
### Serialized Attributes
|
150
|
+
|
151
|
+
Supplying all the fictional data for your different children can be tedious and ugly. You may want to store canned attributes for your objects in YAML files, similar to fixtures. This features is completely optional. The attributes will get passed to your builders' #build! method as the second argument. An empty hash will be supplied if ObjectMomma couldn't find and parse the attributes for you. Example:
|
152
|
+
|
153
|
+
# spec/object_momma/user_builder.rb
|
154
|
+
class ObjectMomma::UserBuilder
|
155
|
+
…
|
156
|
+
|
157
|
+
def build!(user, attributes)
|
158
|
+
user.attributes = attributes
|
159
|
+
end
|
160
|
+
|
161
|
+
…
|
162
|
+
end
|
163
|
+
|
164
|
+
The YAML file:
|
165
|
+
|
166
|
+
# spec/object_momma/attributes/users.yml
|
167
|
+
Scott Pilgrim:
|
168
|
+
username: "scott_pilgrim"
|
169
|
+
email: "spilgrim@zz.zzz"
|
170
|
+
|
171
|
+
To use:
|
172
|
+
|
173
|
+
=> ObjectMother.spawn("Scott Pilgrim")
|
174
|
+
-> #<User:0xdeadbeef @username="scott_pilgrim", @email="spilgrim@zz.zzz", …>
|
175
|
+
|
176
|
+
### Business in the front, party in the back
|
177
|
+
|
178
|
+
If you'd like to refer to ObjectMomma via the constant ObjectMother, then have it your way:
|
179
|
+
|
180
|
+
In `spec/spec_helper.rb`, add `ObjectMomma.mullet!` somewhere. Then:
|
181
|
+
|
182
|
+
=> ObjectMother.spawn_user("Joe Dirt")
|
183
|
+
|
184
|
+
## More Information
|
185
|
+
|
186
|
+
* [ObjectMomma by Martin Fowler](http://martinfowler.com/bliki/ObjectMother.html)
|
187
|
+
|
188
|
+
### Credits
|
189
|
+
|
190
|
+
ObjectMomma was written by Nathan Ladd, with help from a few partners in crime:
|
191
|
+
|
192
|
+
* Josh Flanagan (jflanagan on github)
|
193
|
+
* Theo Mills
|
194
|
+
|
195
|
+
And, of course, I read about the ObjectMother pattern that I ruined^H^H^H^Himplemented from:
|
196
|
+
|
197
|
+
* Martin Fowler
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
task :console do
|
5
|
+
require "pp"
|
6
|
+
require "irb"
|
7
|
+
|
8
|
+
require "object_momma"
|
9
|
+
ObjectMomma.mullet!
|
10
|
+
|
11
|
+
require File.join(File.dirname(__FILE__), "spec/fixtures/blog_post_voting_classes")
|
12
|
+
|
13
|
+
ARGV.clear
|
14
|
+
IRB.start
|
15
|
+
end
|
16
|
+
|
17
|
+
require "rspec/core/rake_task"
|
18
|
+
RSpec::Core::RakeTask.new('spec')
|
19
|
+
|
20
|
+
task :default => :spec
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module ObjectMomma
|
2
|
+
VALID_IDENTIFIER_CHARS = %q{-\w\s_'\"\.}
|
3
|
+
|
4
|
+
class Builder
|
5
|
+
extend ObjectMomma::ClassAttributes
|
6
|
+
|
7
|
+
class_attribute :child_id_serializer, :siblings
|
8
|
+
|
9
|
+
attr_reader :child
|
10
|
+
private :child
|
11
|
+
|
12
|
+
def build_child_from_hash(hash, &block)
|
13
|
+
if has_child_id_serializer?
|
14
|
+
child.child_id = self.class.run_with_binding(hash)
|
15
|
+
|
16
|
+
hash.each do |name, value|
|
17
|
+
if has_siblings?
|
18
|
+
sibling_object_type = self.class.siblings[name]
|
19
|
+
if sibling_object_type
|
20
|
+
if value.is_a?(ObjectMomma::Child)
|
21
|
+
child = value
|
22
|
+
else
|
23
|
+
child = yield(sibling_object_type, value)
|
24
|
+
end
|
25
|
+
value = child.child_object
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
ivar_name = "@#{name}"
|
30
|
+
instance_variable_set(ivar_name, value)
|
31
|
+
self.singleton_class.instance_exec(name, ivar_name) do |name, ivar_name|
|
32
|
+
define_method(name) { instance_variable_get(ivar_name) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
child.child_id = hash.delete(:child_id)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def build!(*args)
|
41
|
+
raise Objectmomma::SubclassNotImplemented
|
42
|
+
end
|
43
|
+
|
44
|
+
def child_id
|
45
|
+
child.child_id
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(child)
|
49
|
+
@child = child
|
50
|
+
end
|
51
|
+
|
52
|
+
def is_persisted?(object)
|
53
|
+
if object.respond_to?(:persisted?)
|
54
|
+
object.persisted?
|
55
|
+
else
|
56
|
+
raise ObjectMomma::SubclassNotImplemented, "Override #is_persisted? "\
|
57
|
+
"to support objects that do not respond to #persisted?"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.builder_for(object_type)
|
62
|
+
if ObjectMomma.builder_path
|
63
|
+
builder_file = File.join(ObjectMomma.builder_path, "#{object_type}_builder.rb")
|
64
|
+
require builder_file if File.size?(builder_file)
|
65
|
+
end
|
66
|
+
|
67
|
+
classified_name = "_#{object_type}Builder".gsub(/_\w/) do |underscored|
|
68
|
+
underscored[1].upcase
|
69
|
+
end
|
70
|
+
|
71
|
+
ObjectMomma.const_get(classified_name)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.has_child_id_serializer?
|
75
|
+
self.child_id_serializer.respond_to?(:to_proc)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.has_siblings?
|
79
|
+
self.siblings.is_a?(Hash)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.run_with_binding(props = {}, &block)
|
83
|
+
Object.new.tap do |o|
|
84
|
+
props.each do |name, value|
|
85
|
+
ivar_name = "@#{name}".to_sym
|
86
|
+
o.singleton_class.class_eval do
|
87
|
+
define_method(name) { instance_variable_get(ivar_name) }
|
88
|
+
end
|
89
|
+
o.instance_variable_set(ivar_name, value)
|
90
|
+
end
|
91
|
+
|
92
|
+
o.singleton_class.class_eval(&block) if block_given?
|
93
|
+
end.instance_exec(&child_id_serializer)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.string_to_hash(object_type, string)
|
97
|
+
builder = builder_for(object_type)
|
98
|
+
|
99
|
+
if builder.has_child_id_serializer?
|
100
|
+
vars = []
|
101
|
+
|
102
|
+
builder.run_with_binding(vars: vars) do
|
103
|
+
def method_missing(sym, *args, &block)
|
104
|
+
return super unless args.empty? && !block_given?
|
105
|
+
vars << sym unless vars.include?(sym)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
string_matcher = "([#{VALID_IDENTIFIER_CHARS}]+)"
|
110
|
+
regex_string = builder.run_with_binding(string_matcher: string_matcher) do
|
111
|
+
def method_missing(sym, *args, &block)
|
112
|
+
return super unless args.empty? && !block_given?
|
113
|
+
string_matcher
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
regex = Regexp.new("^#{regex_string}$")
|
118
|
+
matches = string.match(regex).to_a[1..-1]
|
119
|
+
|
120
|
+
if matches.nil?
|
121
|
+
raise BadChildIdentifier, "Bad child_id `#{string}' for builder "\
|
122
|
+
"`#{name}'"
|
123
|
+
end
|
124
|
+
|
125
|
+
Hash[vars.zip(matches)]
|
126
|
+
else
|
127
|
+
{child_id: string}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class << self
|
132
|
+
def child_id(&block)
|
133
|
+
self.child_id_serializer = block
|
134
|
+
end
|
135
|
+
|
136
|
+
def has_siblings(*args)
|
137
|
+
if args.last.is_a?(Hash)
|
138
|
+
hash = args.pop.each_with_object({}) do |(sibling_name, object_type), hash|
|
139
|
+
hash[sibling_name] = object_type
|
140
|
+
end
|
141
|
+
else
|
142
|
+
hash = {}
|
143
|
+
end
|
144
|
+
|
145
|
+
args.each_with_object(hash) do |sibling_name|
|
146
|
+
object_type = sibling_name
|
147
|
+
hash[sibling_name] = object_type
|
148
|
+
end
|
149
|
+
|
150
|
+
self.siblings = hash
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def has_child_id_serializer?
|
157
|
+
self.class.has_child_id_serializer?
|
158
|
+
end
|
159
|
+
|
160
|
+
def has_siblings?
|
161
|
+
self.class.has_siblings?
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module ObjectMomma
|
2
|
+
ACTUALIZE_STRATEGIES = [:create, :find, :find_or_create]
|
3
|
+
|
4
|
+
class Child
|
5
|
+
attr_accessor :child_id
|
6
|
+
attr_reader :actualize_strategy, :builder, :object_type
|
7
|
+
alias_method :to_s, :child_id
|
8
|
+
|
9
|
+
def initialize(object_type, hash, actualize_strategy)
|
10
|
+
unless ACTUALIZE_STRATEGIES.include?(actualize_strategy)
|
11
|
+
raise ArgumentError, "Invalid actualize strategy "\
|
12
|
+
"`#{actualize_strategy}'; valid values are "\
|
13
|
+
"#{ACTUALIZE_STRATEGIES.map(&:to_s).join(', ')}"
|
14
|
+
end
|
15
|
+
|
16
|
+
@actualize_strategy = actualize_strategy
|
17
|
+
@builder = ObjectMomma.builder_for(object_type).new(self)
|
18
|
+
@object_type = object_type
|
19
|
+
|
20
|
+
builder.build_child_from_hash(hash) do |sibling_object_type, sibling_id|
|
21
|
+
self.class.new(sibling_object_type, sibling_id, @actualize_strategy)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def child_object
|
26
|
+
@child_object ||= actualize_child_object
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
alias_method :original_new, :new
|
31
|
+
|
32
|
+
def new(object_type, string_or_hash, *args)
|
33
|
+
if string_or_hash.is_a?(String)
|
34
|
+
hash = Builder.string_to_hash(object_type, string_or_hash)
|
35
|
+
elsif string_or_hash.is_a?(Hash)
|
36
|
+
hash = string_or_hash
|
37
|
+
else
|
38
|
+
raise ArgumentError, "Must instantiate a Child with a String or a "\
|
39
|
+
"Hash, not a #{string_or_hash.class.name}"
|
40
|
+
end
|
41
|
+
|
42
|
+
original_new(object_type, hash, *args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def actualize_child_object
|
49
|
+
object = builder.first_or_initialize
|
50
|
+
|
51
|
+
if object.respond_to?(:first_or_initialize)
|
52
|
+
object = object.first_or_initialize
|
53
|
+
end
|
54
|
+
|
55
|
+
if builder.is_persisted?(object)
|
56
|
+
if actualize_strategy == :create
|
57
|
+
raise ObjectMomma::ObjectExists, "Child `#{child_id}' created by "\
|
58
|
+
"`#{builder.class.name}' exists already"
|
59
|
+
end
|
60
|
+
else
|
61
|
+
if actualize_strategy == :find
|
62
|
+
raise ObjectMomma::ObjectNotFound, "Child `#{child_id}' created by "\
|
63
|
+
"`#{builder.class.name}' does not yet exist"
|
64
|
+
end
|
65
|
+
|
66
|
+
# arity of -2: def build(object, attrs = {}) (optional)
|
67
|
+
# arity of 2: def build(object, attrs) (required)
|
68
|
+
if [-2, 2].include?(builder.method(:build!).arity)
|
69
|
+
builder.build!(object, attributes_for_child)
|
70
|
+
else
|
71
|
+
builder.build!(object)
|
72
|
+
end
|
73
|
+
|
74
|
+
unless builder.is_persisted?(object)
|
75
|
+
raise ObjectMomma::NotPersisted, "Child `#{child_id}' was created "\
|
76
|
+
"by `#{builder.class.name}' but wasn't persisted"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if builder.respond_to?(:decorate!)
|
81
|
+
builder.decorate!(object)
|
82
|
+
end
|
83
|
+
|
84
|
+
object
|
85
|
+
end
|
86
|
+
|
87
|
+
def attributes_for_child
|
88
|
+
return {} unless ObjectMomma.use_serialized_attributes
|
89
|
+
|
90
|
+
# Pluralize
|
91
|
+
if object_type.to_s.chars.to_a.last == "s"
|
92
|
+
file_name = object_type
|
93
|
+
else
|
94
|
+
file_name = "#{object_type}s"
|
95
|
+
end
|
96
|
+
|
97
|
+
path = File.join(ObjectMomma.serialized_attributes_path, "#{file_name}.yml")
|
98
|
+
|
99
|
+
if File.size?(path)
|
100
|
+
File.open(path) do |yml_file|
|
101
|
+
attributes_by_child_id = YAML::load(yml_file)
|
102
|
+
return recursively_symbolize_hash(attributes_by_child_id.fetch(child_id, {}))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
{}
|
107
|
+
end
|
108
|
+
|
109
|
+
def recursively_symbolize_hash(hash = {})
|
110
|
+
recurse = lambda { |in_hash|
|
111
|
+
{}.tap do |out_hash|
|
112
|
+
in_hash.each do |key, in_value|
|
113
|
+
out_value = in_value.is_a?(Hash) ? recurse.call(in_value) : in_value.dup
|
114
|
+
if key.respond_to?(:to_sym)
|
115
|
+
out_hash[key.to_sym] = out_value
|
116
|
+
else
|
117
|
+
out_hash[key] = out_value
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
}
|
122
|
+
|
123
|
+
recurse.call(hash)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ObjectMomma
|
2
|
+
module ClassAttributes
|
3
|
+
# See http://www.ruby-forum.com/topic/197051
|
4
|
+
def class_attribute(*attributes)
|
5
|
+
singleton_class.class_eval do
|
6
|
+
attr_accessor *attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
@class_attributes ||= []
|
10
|
+
@class_attributes.concat(attributes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def inherited(subclass)
|
14
|
+
@class_attributes.each do |attribute|
|
15
|
+
subclass.send("#{attribute}=", self.send(attribute))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ObjectMomma
|
2
|
+
module Config
|
3
|
+
def self.extended(base)
|
4
|
+
base.singleton_class.instance_eval do
|
5
|
+
attr_reader :builder_path
|
6
|
+
attr_reader :serialized_attributes_path, :use_serialized_attributes
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def builder_path=(path)
|
11
|
+
unless path.nil? || File.directory?(path)
|
12
|
+
raise ArgumentError, "`#{path}' is not a valid directory"
|
13
|
+
end
|
14
|
+
@builder_path = path
|
15
|
+
end
|
16
|
+
|
17
|
+
def serialized_attributes_path=(path)
|
18
|
+
unless File.directory?(path)
|
19
|
+
raise ArgumentError, "`#{path}' is not a valid directory"
|
20
|
+
end
|
21
|
+
@serialized_attributes_path = path
|
22
|
+
end
|
23
|
+
|
24
|
+
def use_serialized_attributes=(true_or_false)
|
25
|
+
@use_serialized_attributes = true_or_false ? true : false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module ObjectMomma
|
2
|
+
module ModuleMethods
|
3
|
+
def builder_for(object_type)
|
4
|
+
ObjectMomma::Builder.builder_for(object_type)
|
5
|
+
end
|
6
|
+
|
7
|
+
def method_missing(method_name, *args, &block)
|
8
|
+
return super unless respond_to?(method_name)
|
9
|
+
return super if block_given?
|
10
|
+
|
11
|
+
object_type, actualize_strategy = object_type_and_actualize_strategy_from_method_name(method_name)
|
12
|
+
args.push(actualize_strategy)
|
13
|
+
|
14
|
+
child = ObjectMomma::Child.new(object_type, *args)
|
15
|
+
child.child_object
|
16
|
+
end
|
17
|
+
|
18
|
+
def mullet!
|
19
|
+
return false if Object.const_defined?(:ObjectMother)
|
20
|
+
|
21
|
+
object_mother = Class.new(BasicObject) do
|
22
|
+
def self.method_missing(*args)
|
23
|
+
ObjectMomma.send(*args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Object.const_set(:ObjectMother, object_mother)
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def object_type_and_actualize_strategy_from_method_name(method_name)
|
32
|
+
# Try ObjectMomma.user
|
33
|
+
begin
|
34
|
+
builder_for(method_name)
|
35
|
+
object_type = method_name.to_sym
|
36
|
+
return [object_type, :find_or_create]
|
37
|
+
rescue NameError
|
38
|
+
end
|
39
|
+
|
40
|
+
# Try ObjectMomma.spawn_user, ObjectMomma.find_user
|
41
|
+
public_method_name, object_type = [*method_name.to_s.match(/^(create|find|spawn)_(\w+)$/).to_a[1..-1]].compact.map(&:to_sym)
|
42
|
+
return nil if object_type.nil?
|
43
|
+
|
44
|
+
begin
|
45
|
+
builder_for(object_type)
|
46
|
+
if public_method_name == :spawn
|
47
|
+
actualize_strategy = :find_or_create
|
48
|
+
else
|
49
|
+
actualize_strategy = public_method_name
|
50
|
+
end
|
51
|
+
[object_type, actualize_strategy]
|
52
|
+
rescue NameError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
alias_method :parse_method_name, :object_type_and_actualize_strategy_from_method_name
|
57
|
+
|
58
|
+
def respond_to?(method_name, *args)
|
59
|
+
return true if super
|
60
|
+
parse_method_name(method_name).nil? ? false : true
|
61
|
+
end
|
62
|
+
|
63
|
+
def spawn(hash = {})
|
64
|
+
hash.each do |object_type, child_id_or_ids|
|
65
|
+
begin
|
66
|
+
builder_for(object_type)
|
67
|
+
rescue NameError => ne
|
68
|
+
singularized = object_type.to_s.chomp('s').to_sym
|
69
|
+
raise ne if singularized == object_type
|
70
|
+
|
71
|
+
builder_for(singularized)
|
72
|
+
object_type = singularized
|
73
|
+
end
|
74
|
+
|
75
|
+
child_ids = [*child_id_or_ids]
|
76
|
+
child_ids.each { |child_id| send("spawn_#{object_type}", child_id) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/object_momma.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
require 'object_momma/class_attributes'
|
4
|
+
|
5
|
+
require 'object_momma/builder'
|
6
|
+
require 'object_momma/config'
|
7
|
+
require 'object_momma/child'
|
8
|
+
require 'object_momma/module_methods'
|
9
|
+
require 'object_momma/version'
|
10
|
+
|
11
|
+
module ObjectMomma
|
12
|
+
BadChildIdentifier = Class.new(StandardError)
|
13
|
+
BadSerializer = Class.new(StandardError)
|
14
|
+
NotPersisted = Class.new(StandardError)
|
15
|
+
ObjectExists = Class.new(StandardError)
|
16
|
+
ObjectNotFound = Class.new(StandardError)
|
17
|
+
SubclassNotImplemented = Class.new(StandardError)
|
18
|
+
|
19
|
+
self.extend(ObjectMomma::ModuleMethods)
|
20
|
+
self.extend(ObjectMomma::Config)
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/object_momma/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["ntl"]
|
6
|
+
gem.email = ["nathanladd@gmail.com"]
|
7
|
+
gem.description = %q{object_momma is an Object Mother implementation in ruby}
|
8
|
+
gem.summary = %q{object_momma is an Object Mother implementation in ruby}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "object_momma"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = ObjectMomma::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
File.dirname(__FILE__).tap do |current_directory|
|
2
|
+
%w{fake_model post user comment vote}.each do |file|
|
3
|
+
require File.join(current_directory, file)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module ObjectMomma
|
8
|
+
class VoteBuilder < Builder
|
9
|
+
has_siblings :comment, :voter => :user
|
10
|
+
child_id { "#{voter}'s #{vote_type} for #{comment}" }
|
11
|
+
|
12
|
+
def first_or_initialize
|
13
|
+
Vote.where({
|
14
|
+
comment_id: comment.id,
|
15
|
+
user_id: voter.id,
|
16
|
+
type: vote_type.downcase
|
17
|
+
})
|
18
|
+
end
|
19
|
+
|
20
|
+
def decorate(vote)
|
21
|
+
# The regular Vote class doesn't implement this method, but Vote objects
|
22
|
+
# instantiated via ObjectMomma will get this behavior. Be careful with
|
23
|
+
# this feature, it can make a mess!
|
24
|
+
def vote.switch_vote!
|
25
|
+
if upvote?
|
26
|
+
self.type = 'downvote'
|
27
|
+
else
|
28
|
+
self.type = 'upvote'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def build!(vote)
|
34
|
+
# This is demonstration only. In a real ActiveRecord scenario, that
|
35
|
+
# scope returned by #first_or_initialize would have set this stuff for
|
36
|
+
# us. It may be possible to automatically do this in a generic way.
|
37
|
+
vote.comment = self.comment
|
38
|
+
vote.type = self.vote_type
|
39
|
+
vote.user = self.voter
|
40
|
+
|
41
|
+
# This is basic "set my data according to who I am" logic:
|
42
|
+
if vote.user.politician?
|
43
|
+
vote.switch_vote!
|
44
|
+
end
|
45
|
+
|
46
|
+
vote.save!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class UserBuilder < Builder
|
51
|
+
def first_or_initialize
|
52
|
+
User.where(email: test_email)
|
53
|
+
end
|
54
|
+
|
55
|
+
def decorate!(user)
|
56
|
+
def user.politician?
|
57
|
+
full_name == "John Adams"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def build!(user, attributes = {})
|
62
|
+
# ActiveRecord's scope returned by #first_or_initialize would do this for
|
63
|
+
# us under normal circumstances.
|
64
|
+
user.email = attributes[:email] || test_email
|
65
|
+
user.username = attributes[:username] || test_username
|
66
|
+
user.full_name = child_id
|
67
|
+
|
68
|
+
user.save!
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# A child id of 'Scott Pilgrim' becomes 'scottpilgrim@zz.zzz'
|
74
|
+
def test_email
|
75
|
+
"#{test_username}@zz.zzz"
|
76
|
+
end
|
77
|
+
|
78
|
+
# A child id of 'Scott Pilgrim' becomes 'scottpilgrim'
|
79
|
+
def test_username
|
80
|
+
self.child_id.split(' ', 2).map(&:downcase).join
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class PostBuilder < Builder
|
85
|
+
child_id { "Post about #{subject}" }
|
86
|
+
|
87
|
+
def first_or_initialize
|
88
|
+
Post.where(title: title_from_subject)
|
89
|
+
end
|
90
|
+
|
91
|
+
def build!(post)
|
92
|
+
post.title = title_from_subject
|
93
|
+
post.subject = self.subject
|
94
|
+
post.body = body_from_subject
|
95
|
+
|
96
|
+
post.save!
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def body_from_subject
|
102
|
+
case self.subject
|
103
|
+
when "Comic Books" then "Batman is the best comic book of all time"
|
104
|
+
when "Politics" then "John Adams was a great leader."
|
105
|
+
else "Lorem Ipsum"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def title_from_subject
|
110
|
+
case self.subject
|
111
|
+
when "Comic Books" then "Batman"
|
112
|
+
else "My Thoughts on #{self.subject}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class CommentBuilder < Builder
|
118
|
+
has_siblings :post, :author => :user
|
119
|
+
child_id { "#{author}'s Comment on #{post}" }
|
120
|
+
|
121
|
+
def first_or_initialize
|
122
|
+
Comment.where({
|
123
|
+
post_id: post.id,
|
124
|
+
user_id: author.id
|
125
|
+
})
|
126
|
+
end
|
127
|
+
|
128
|
+
def build!(comment)
|
129
|
+
comment.post = self.post
|
130
|
+
comment.user = self.author
|
131
|
+
|
132
|
+
comment.save!
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module FakeModel
|
2
|
+
def self.included(base)
|
3
|
+
base.extend(ClassMethods)
|
4
|
+
end
|
5
|
+
|
6
|
+
def ==(other_object)
|
7
|
+
self.object_id == other_object.object_id
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(attributes = {})
|
11
|
+
attributes.each do |key, value|
|
12
|
+
instance_variable_set("@#{key}".to_sym, value)
|
13
|
+
end
|
14
|
+
|
15
|
+
self.class.instances << self
|
16
|
+
end
|
17
|
+
|
18
|
+
def id
|
19
|
+
return nil unless persisted?
|
20
|
+
|
21
|
+
persisted_instances = self.class.instances.select(&:persisted?)
|
22
|
+
index = persisted_instances.index { |instance| instance == self }
|
23
|
+
index + 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def persisted?
|
27
|
+
@is_persisted ? true : false
|
28
|
+
end
|
29
|
+
|
30
|
+
def save
|
31
|
+
@is_persisted = true
|
32
|
+
true
|
33
|
+
end
|
34
|
+
alias_method :save!, :save
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
def belongs_to(association_name)
|
38
|
+
define_method(association_name) do
|
39
|
+
instance_variable_get("@#{association_name}")
|
40
|
+
end
|
41
|
+
|
42
|
+
define_method("#{association_name}_id") do
|
43
|
+
send(association_name).id
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method("#{association_name}=") do |value|
|
47
|
+
instance_variable_set("@#{association_name}", value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy_all
|
52
|
+
instances.clear
|
53
|
+
end
|
54
|
+
|
55
|
+
def instances
|
56
|
+
@instances ||= []
|
57
|
+
end
|
58
|
+
|
59
|
+
def fake_column(column_name)
|
60
|
+
class_eval { attr_accessor(column_name) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def fake_columns(*column_names)
|
64
|
+
column_names.each { |column_name| fake_column(column_name) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def where(conditions = {})
|
68
|
+
object = instances.detect do |object|
|
69
|
+
conditions.all? { |attr, value| object.send(attr) == value }
|
70
|
+
end
|
71
|
+
|
72
|
+
object ||= self.new
|
73
|
+
|
74
|
+
scope = BasicObject.new
|
75
|
+
|
76
|
+
scope.instance_exec(object) { |o| @__scope_object__ = o }
|
77
|
+
|
78
|
+
class << scope
|
79
|
+
def first_or_initialize
|
80
|
+
@__scope_object__
|
81
|
+
end
|
82
|
+
def method_missing(method_name, *args, &block)
|
83
|
+
@__scope_object__.send(method_name, *args, &block)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
scope
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class ObjectMomma::ThingBuilder < ObjectMomma::Builder
|
2
|
+
class Thing
|
3
|
+
attr_accessor :a_property, :another_property
|
4
|
+
|
5
|
+
def persisted?
|
6
|
+
a_property.nil? ? false : true
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def first_or_initialize
|
11
|
+
Thing.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def build!(thing)
|
15
|
+
thing.a_property = :foo
|
16
|
+
thing.another_property = :bar
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'object_momma'
|
2
|
+
require File.join(File.dirname(__FILE__), 'fixtures/blog_post_voting_classes')
|
3
|
+
|
4
|
+
describe ObjectMomma do
|
5
|
+
before do
|
6
|
+
User.destroy_all
|
7
|
+
Post.destroy_all
|
8
|
+
Comment.destroy_all
|
9
|
+
Vote.destroy_all
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:vote_child_id) do
|
13
|
+
"Billy Pilgrim's Upvote for Scott Pilgrim's Comment on Post about Comic Books"
|
14
|
+
end
|
15
|
+
|
16
|
+
context "A simple object type (User)" do
|
17
|
+
shared_examples_for "Scott Pilgrim" do
|
18
|
+
it("is a User") { user.should be_a(User) }
|
19
|
+
it("has its' email set properly") { user.email.should == "scottpilgrim@zz.zzz" }
|
20
|
+
it("has its' username set properly") { user.username.should == "scottpilgrim" }
|
21
|
+
it("is persisted") { user ; ObjectMomma.find_user("Scott Pilgrim") }
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when instantiated with a Hash" do
|
25
|
+
let(:user) { ObjectMomma.spawn_user(child_id: "Scott Pilgrim") }
|
26
|
+
it_behaves_like "Scott Pilgrim"
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when instantiated with a String" do
|
30
|
+
let(:user) { ObjectMomma.spawn_user("Scott Pilgrim") }
|
31
|
+
it_behaves_like "Scott Pilgrim"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "An object type that has a child id constructor (Post)" do
|
36
|
+
shared_examples_for "Post about Comic Books" do
|
37
|
+
it("is a Post") { post.should be_a(Post) }
|
38
|
+
it("has its' title set properly") { post.title.should == "Batman" }
|
39
|
+
it("has its' subject set properly") { post.subject.should == "Comic Books" }
|
40
|
+
it("is persisted") { post ; ObjectMomma.find_post("Post about Comic Books") }
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when instantiated with a Hash" do
|
44
|
+
let(:post) { ObjectMomma.spawn_post(subject: "Comic Books") }
|
45
|
+
it_behaves_like "Post about Comic Books"
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when instantiated with a String" do
|
49
|
+
let(:post) { ObjectMomma.spawn_post("Post about Comic Books") }
|
50
|
+
it_behaves_like "Post about Comic Books"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "An object with simple siblings (Comment)" do
|
55
|
+
shared_examples_for "Scott Pilgrim's Comment on Post about Comic Books" do
|
56
|
+
it("is a Comment") { comment.should be_a(Comment) }
|
57
|
+
it("has its' user set properly") do
|
58
|
+
comment.user.object_id.should == ObjectMomma.find_user("Scott Pilgrim").object_id
|
59
|
+
end
|
60
|
+
it("has its' post set properly") do
|
61
|
+
comment.post.object_id.should == ObjectMomma.find_post("Post about Comic Books").object_id
|
62
|
+
end
|
63
|
+
it("is persisted") do
|
64
|
+
comment
|
65
|
+
ObjectMomma.find_comment("Scott Pilgrim's Comment on Post about Comic Books")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when instantiated with a Hash" do
|
70
|
+
let(:comment) do
|
71
|
+
ObjectMomma.spawn_comment({
|
72
|
+
author: "Scott Pilgrim",
|
73
|
+
post: "Post about Comic Books"
|
74
|
+
})
|
75
|
+
end
|
76
|
+
it_behaves_like "Scott Pilgrim's Comment on Post about Comic Books"
|
77
|
+
end
|
78
|
+
|
79
|
+
context "when instantiated with a String" do
|
80
|
+
let(:comment) do
|
81
|
+
ObjectMomma.spawn_comment("Scott Pilgrim's Comment on Post about Comic Books")
|
82
|
+
end
|
83
|
+
it_behaves_like "Scott Pilgrim's Comment on Post about Comic Books"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "An object with nested siblings (Vote)" do
|
88
|
+
context "when instantiated with a String" do
|
89
|
+
let(:vote) do
|
90
|
+
ObjectMomma.spawn_vote(vote_child_id)
|
91
|
+
end
|
92
|
+
it("is a Vote") { vote.should be_a(Vote) }
|
93
|
+
it("has its' user set properly") do
|
94
|
+
vote.user.object_id.should == ObjectMomma.find_user("Billy Pilgrim").object_id
|
95
|
+
end
|
96
|
+
it("has its' comment set properly") do
|
97
|
+
vote.comment.object_id.should == ObjectMomma.find_comment("Scott Pilgrim's Comment on Post about Comic Books").object_id
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "An object with serialized attributes" do
|
103
|
+
before do
|
104
|
+
ObjectMomma.use_serialized_attributes = true
|
105
|
+
ObjectMomma.serialized_attributes_path = File.expand_path("../fixtures", __FILE__)
|
106
|
+
end
|
107
|
+
|
108
|
+
after do
|
109
|
+
ObjectMomma.use_serialized_attributes = false
|
110
|
+
end
|
111
|
+
|
112
|
+
let(:user) { ObjectMomma.spawn_user("Scott Pilgrim") }
|
113
|
+
|
114
|
+
it "pulls in the attributes and loads them into the object" do
|
115
|
+
user.email.should == "foobar@zz.zzz"
|
116
|
+
user.username.should == "lovemuscle23"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "An object whose builder is stored in the builder path" do
|
121
|
+
after do
|
122
|
+
ObjectMomma.builder_path = nil
|
123
|
+
end
|
124
|
+
|
125
|
+
it "Loads the builder ruby file from the path only after builder path is set" do
|
126
|
+
lambda {
|
127
|
+
ObjectMomma.spawn_thing("The Thing")
|
128
|
+
}.should raise_error(NoMethodError)
|
129
|
+
|
130
|
+
ObjectMomma.builder_path = File.expand_path("../fixtures", __FILE__)
|
131
|
+
|
132
|
+
thing = ObjectMomma.spawn_thing("The Thing")
|
133
|
+
|
134
|
+
thing.a_property.should == :foo
|
135
|
+
thing.another_property.should == :bar
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context ".spawn" do
|
140
|
+
it "invokes #spawn_(object_type) for the supplied arguments" do
|
141
|
+
ObjectMomma.should_receive(:spawn_user).with("Billy Pilgrim").once
|
142
|
+
ObjectMomma.should_receive(:spawn_user).with("Scott Pilgrim").once
|
143
|
+
ObjectMomma.should_receive(:spawn_vote).with(vote_child_id).once
|
144
|
+
ObjectMomma.should_receive(:spawn_comment).with("Post about Politics").once
|
145
|
+
|
146
|
+
ObjectMomma.spawn({
|
147
|
+
users: ["Billy Pilgrim", "Scott Pilgrim"],
|
148
|
+
vote: vote_child_id,
|
149
|
+
comment: "Post about Politics"
|
150
|
+
})
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context ".spawn_(object_type)" do
|
155
|
+
it "raises an ObjectMomma::BadChildIdentifier when it cannot parse child id" do
|
156
|
+
lambda {
|
157
|
+
ObjectMomma.spawn_post("Poast about Birds")
|
158
|
+
}.should raise_error(ObjectMomma::BadChildIdentifier)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context ".mullet!" do
|
163
|
+
before do
|
164
|
+
ObjectMomma.mullet!
|
165
|
+
end
|
166
|
+
|
167
|
+
after do
|
168
|
+
if Object.const_defined?(:ObjectMother)
|
169
|
+
Object.send(:remove_const, :ObjectMother)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it "defines ObjectMother" do
|
174
|
+
Object.const_defined?(:ObjectMother).should be_true
|
175
|
+
end
|
176
|
+
|
177
|
+
it "delegates everything to ObjectMomma" do
|
178
|
+
ObjectMomma.should_receive(:fizzle).with(:shizzle)
|
179
|
+
ObjectMother.fizzle(:shizzle)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context ".find_(object_type)" do
|
184
|
+
it "raises an ObjectMomma::ObjectNotFound exception when object does not exist" do
|
185
|
+
lambda {
|
186
|
+
ObjectMomma.find_user("Scott Pilgrim")
|
187
|
+
}.should raise_error(ObjectMomma::ObjectNotFound)
|
188
|
+
end
|
189
|
+
|
190
|
+
it "does not raise an ObjectMomma::ObjectNotFound exception when object does exist" do
|
191
|
+
ObjectMomma.spawn_user("Scott Pilgrim")
|
192
|
+
lambda {
|
193
|
+
ObjectMomma.find_user("Scott Pilgrim")
|
194
|
+
}.should_not raise_error(ObjectMomma::ObjectNotFound)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context ".create_(object_type)" do
|
199
|
+
it "does not raise an ObjectMomma::ObjectExists exception when object does not exist" do
|
200
|
+
lambda {
|
201
|
+
ObjectMomma.create_user("Scott Pilgrim")
|
202
|
+
}.should_not raise_error(ObjectMomma::ObjectExists)
|
203
|
+
end
|
204
|
+
|
205
|
+
it "raises an ObjectMomma::ObjectExists exception when object does exist" do
|
206
|
+
ObjectMomma.spawn_user("Scott Pilgrim")
|
207
|
+
lambda {
|
208
|
+
ObjectMomma.create_user("Scott Pilgrim")
|
209
|
+
}.should raise_error(ObjectMomma::ObjectExists)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context ".(object_type)" do
|
214
|
+
it "creates objects that don't exist, and finds objects that do" do
|
215
|
+
lambda {
|
216
|
+
ObjectMomma.user("Scott Pilgrim")
|
217
|
+
}.should_not raise_error(ObjectMomma::ObjectNotFound)
|
218
|
+
lambda {
|
219
|
+
ObjectMomma.user("Scott Pilgrim")
|
220
|
+
}.should_not raise_error(ObjectMomma::ObjectExists)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
data/tmp/.gitkeep
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: object_momma
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 9
|
8
|
+
- 0
|
9
|
+
version: 0.9.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- ntl
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-11-28 00:00:00 -06:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :development
|
31
|
+
version_requirements: *id001
|
32
|
+
description: object_momma is an Object Mother implementation in ruby
|
33
|
+
email:
|
34
|
+
- nathanladd@gmail.com
|
35
|
+
executables: []
|
36
|
+
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files: []
|
40
|
+
|
41
|
+
files:
|
42
|
+
- .gitignore
|
43
|
+
- Gemfile
|
44
|
+
- LICENSE
|
45
|
+
- README.md
|
46
|
+
- Rakefile
|
47
|
+
- lib/object_momma.rb
|
48
|
+
- lib/object_momma/builder.rb
|
49
|
+
- lib/object_momma/child.rb
|
50
|
+
- lib/object_momma/class_attributes.rb
|
51
|
+
- lib/object_momma/config.rb
|
52
|
+
- lib/object_momma/module_methods.rb
|
53
|
+
- lib/object_momma/version.rb
|
54
|
+
- object_momma.gemspec
|
55
|
+
- spec/fixtures/blog_post_voting_classes.rb
|
56
|
+
- spec/fixtures/comment.rb
|
57
|
+
- spec/fixtures/fake_model.rb
|
58
|
+
- spec/fixtures/post.rb
|
59
|
+
- spec/fixtures/thing_builder.rb
|
60
|
+
- spec/fixtures/user.rb
|
61
|
+
- spec/fixtures/users.yml
|
62
|
+
- spec/fixtures/vote.rb
|
63
|
+
- spec/object_momma_spec.rb
|
64
|
+
- tmp/.gitkeep
|
65
|
+
has_rdoc: true
|
66
|
+
homepage: ""
|
67
|
+
licenses: []
|
68
|
+
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
requirements: []
|
89
|
+
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.3.6
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: object_momma is an Object Mother implementation in ruby
|
95
|
+
test_files:
|
96
|
+
- spec/fixtures/blog_post_voting_classes.rb
|
97
|
+
- spec/fixtures/comment.rb
|
98
|
+
- spec/fixtures/fake_model.rb
|
99
|
+
- spec/fixtures/post.rb
|
100
|
+
- spec/fixtures/thing_builder.rb
|
101
|
+
- spec/fixtures/user.rb
|
102
|
+
- spec/fixtures/users.yml
|
103
|
+
- spec/fixtures/vote.rb
|
104
|
+
- spec/object_momma_spec.rb
|