Skip to content

Commit

Permalink
Adding a sequel adapter. As Sequel follows a different model to Activ…
Browse files Browse the repository at this point in the history
…eRecord, make_unsaved won't work for object graphs, and you can't call make on an association (i.e. post.comments.make).
  • Loading branch information
knaveofdiamonds committed Jun 17, 2009
1 parent d7874ca commit b3587be
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.markdown
Expand Up @@ -65,7 +65,7 @@ Create a `blueprints.rb` file to hold your blueprints in your test (or spec) dir
require 'machinist/active_record'
require 'sham'

Substitute `data_mapper` for `active_record` if that's your weapon of choice.
Substitute `data_mapper` or `sequel` for `active_record` if that's your weapon of choice.

Require `blueprints.rb` in your `test_helper.rb` (or `spec_helper.rb`):

Expand Down Expand Up @@ -153,7 +153,7 @@ If you don't supply a block for an attribute in the blueprint, Machinist will lo
body
end

If you want to generate an object without saving it to the database, replace `make` with `make_unsaved`. (`make_unsaved` also ensures that any associated objects that need to be generated are not saved. See the section on associations below.)
If you want to generate an object without saving it to the database, replace `make` with `make_unsaved`. (`make_unsaved` also ensures that any associated objects that need to be generated are not saved - although not if you are using Sequel. See the section on associations below.)

You can refer to already assigned attributes when constructing a new attribute:

Expand Down
62 changes: 62 additions & 0 deletions lib/machinist/sequel.rb
@@ -0,0 +1,62 @@
require 'machinist'
require 'machinist/blueprints'
require 'sequel'

module Machinist
# Uses sequel (http://sequel.rubyforge.org) as Machinist's ORM.
class SequelAdapter
def self.has_association?(object, attribute)
object.class.associations.include?(attribute)
end

def self.class_for_association(object, attribute)
object.class.association_reflection(attribute).associated_class
end

def self.assigned_attributes_without_associations(lathe)
attributes = {}
lathe.assigned_attributes.each_pair do |attribute, value|
association = lathe.object.class.association_reflection(attribute)
if association && association[:type] == :many_to_one
key = association[:key] || association.default_key
attributes[key] = value.send(association.primary_key)
else
attributes[attribute] = value
end
end
attributes
end
end

module SequelExtensions
def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def make(*args, &block)
lathe = Lathe.run(Machinist::SequelAdapter, self.new, *args)
unless Machinist.nerfed?
lathe.object.save
lathe.object.refresh
end
lathe.object(&block)
end

def make_unsaved(*args)
returning(Machinist.with_save_nerfed { make(*args) }) do |object|
yield object if block_given?
end
end

def plan(*args)
lathe = Lathe.run(Machinist::SequelAdapter, self.new, *args)
Machinist::SequelAdapter.assigned_attributes_without_associations(lathe)
end
end
end
end

class Sequel::Model
include Machinist::SequelExtensions
end
139 changes: 139 additions & 0 deletions spec/sequel_spec.rb
@@ -0,0 +1,139 @@
require File.dirname(__FILE__) + '/spec_helper'
require 'machinist/sequel'

# We have to define this here because Sequel needs a DB connection setup before
# you can define models
DB = Sequel.sqlite(:logger => Logger.new(File.dirname(__FILE__) + "/log/test.log"))

DB.create_table :people do
primary_key :id
String :name
String :type
String :password
Boolean :admin, :default => false
end

DB.create_table :posts do
primary_key :id
String :title
String :body
Boolean :published, :default => true
end

DB.create_table :comments do
primary_key :id
Integer :post_id
Integer :author_id
String :body
end

module MachinistSequelSpecs

class Person < Sequel::Model
set_restricted_columns :password
end

class Post < Sequel::Model
one_to_many :comments, :class => "MachinistSequelSpecs::Comment"
end

class Comment < Sequel::Model
many_to_one :post, :class => "MachinistSequelSpecs::Post"
many_to_one :author, :class => "MachinistSequelSpecs::Person"
end

describe Machinist, "Sequel adapter" do
before(:each) do
Person.clear_blueprints!
Post.clear_blueprints!
Comment.clear_blueprints!
end

describe "make method" do
it "should save the constructed object" do
Person.blueprint { }
person = Person.make
person.should_not be_new
end

it "should create an object through belongs_to association" do
Post.blueprint { }
Comment.blueprint { post }
Comment.make.post.class.should == Post
end

it "should create an object through belongs_to association with a class_name attribute" do
Person.blueprint { }
Comment.blueprint { author }
Comment.make.author.class.should == Person
end

it "should allow setting a protected attribute in the blueprint" do
Person.blueprint do
password "Test"
end
Person.make.password.should == "Test"
end

it "should allow overriding a protected attribute" do
Person.blueprint do
password "Test"
end
Person.make(:password => "New").password.should == "New"
end

it "should allow setting the id attribute in a blueprint" do
Person.blueprint { id 12345 }
Person.make.id.should == 12345
end

# it "should allow setting the type attribute in a blueprint" do
# Person.blueprint { type "Person" }
# Person.make.type.should == "Person"
# end
end

describe "plan method" do
it "should not save the constructed object" do
person_count = Person.count
Person.blueprint { }
person = Person.plan
Person.count.should == person_count
end

it "should create an object through a belongs_to association, and return its id" do
Post.blueprint { }
Comment.blueprint { post }
post_count = Post.count
comment = Comment.plan
Post.count.should == post_count + 1
comment[:post].should be_nil
comment[:post_id].should_not be_nil
end
end


# Note that building up an unsaved object graph using just the association methods
# is not possible in Sequel, so make_unsaved will break in amusing ways unless
# you manually override the setters.
#
# From sequel-talk "Sequel does not have such an API and will not be adding one"
# Feb 17
describe "make_unsaved method" do
it "should not save the constructed object" do
Person.blueprint { }
person = Person.make_unsaved
person.should be_new
end

it "should save objects made within a passed-in block" do
Post.blueprint { }
Comment.blueprint { }
comment = nil
post = Post.make_unsaved { comment = Comment.make }
post.should be_new
comment.should_not be_new
end
end
end
end

0 comments on commit b3587be

Please sign in to comment.