Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Sequel adapter #89

Open
wants to merge 2 commits into from

4 participants

@jfirebaugh

I added a Sequel adapter.

Since Sequel doesn't support deferred associations natively, the adapter is slightly more complex than the ActiveRecord adapter.

John Firebaugh added some commits
@matthauck

Is it possible to get this merged in? It would be really nice to have Sequel support in machinist...

@benhoskings
Collaborator

This would be better packaged as a separate gem, perhaps machinist-sequel—it's cleanly independent of machinist core and this way, the mainenance of one won't affect the other.

@matthauck

Seeing as ActiveRecord is supported as an adapter already, and not really part of the "core", is it not reasonable to have Sequel also supported in the main gem?

@benhoskings
Collaborator
@dhallman

Thanks John for this Sequel adapter. I found one bug in it, that was causing many-to-one associations to render in the database as many-to-many. This was fixed with the following 2 added lines:

   def assign_attribute(attribute, value)
      association = @klass.association_reflection(attribute)
      if association
        if association.returns_array?
          (object.machinist_deferred_associations ||= [])  true)
          super
        # NOTE: next 2 lines were necessary to fix a bug
        #  in the sequel adapter.  Without it, many-to-one
        #  relationships are incorrectly rendered in the
        #  database as a many-to-many.  -Dean
        else
          super
        end
      else
        super
      end
    end
 
Here is a spec that recreates the problem:
      RssFeed.blueprint do
        name { Faker::Lorem.word }
      end

      feed = RssFeed.make!
      20.times { Video.make!( :managed_feed => feed  ) }

Without the above fix, 20 RssFeed rows and 20 video rows are created. Total of 40 rows.

With the above fix, 1 RssFeed is created, and 20 videos, all linked back to the common RssFeed. Total of 21 rows.

Dean

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 13, 2011
  1. Name variables more consistently.

    John Firebaugh authored
  2. Add Sequel adapter.

    John Firebaugh authored
This page is out of date. Refresh to see the latest.
View
2  Gemfile.lock
@@ -32,6 +32,7 @@ GEM
rspec-expectations (2.6.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
+ sequel (3.25.0)
tzinfo (0.3.28)
PLATFORMS
@@ -45,3 +46,4 @@ DEPENDENCIES
rcov
rdoc
rspec
+ sequel
View
10 lib/machinist/lathe.rb
@@ -51,13 +51,13 @@ def make_one_value(attribute, args) #:nodoc:
yield
end
- def assign_attribute(key, value) #:nodoc:
- @assigned_attributes[key.to_sym] = value
- @object.send("#{key}=", value)
+ def assign_attribute(attribute, value) #:nodoc:
+ @assigned_attributes[attribute.to_sym] = value
+ @object.send("#{attribute}=", value)
end
- def attribute_assigned?(key) #:nodoc:
- @assigned_attributes.has_key?(key.to_sym)
+ def attribute_assigned?(attribute) #:nodoc:
+ @assigned_attributes.has_key?(attribute.to_sym)
end
def raise_argument_error(attribute) #:nodoc:
View
16 lib/machinist/sequel.rb
@@ -0,0 +1,16 @@
+require 'sequel'
+require 'machinist'
+require 'machinist/sequel/blueprint'
+require 'machinist/sequel/lathe'
+
+module Sequel #:nodoc:
+ class Model #:nodoc:
+ extend Machinist::Machinable
+
+ def self.blueprint_class
+ Machinist::Sequel::Blueprint
+ end
+
+ attr_accessor :machinist_deferred_associations
+ end
+end
View
22 lib/machinist/sequel/blueprint.rb
@@ -0,0 +1,22 @@
+module Machinist::Sequel
+ class Blueprint < Machinist::Blueprint
+ # Make and save an object.
+ def make!(attributes = {})
+ object = make(attributes)
+ object.save(:raise_on_failure => true)
+
+ deferred_associations = object.machinist_deferred_associations
+ if deferred_associations
+ deferred_associations.each do |association, array|
+ array.each {|e| object.send(association.add_method, e) }
+ end
+ end
+
+ object
+ end
+
+ def lathe_class #:nodoc:
+ Machinist::Sequel::Lathe
+ end
+ end
+end
View
35 lib/machinist/sequel/lathe.rb
@@ -0,0 +1,35 @@
+module Machinist::Sequel
+ class Lathe < Machinist::Lathe
+ def make_one_value(attribute, args) #:nodoc:
+ if block_given?
+ raise_argument_error(attribute) unless args.empty?
+ yield
+ else
+ make_association(attribute, args)
+ end
+ end
+
+ def make_association(attribute, args) #:nodoc:
+ association = @klass.association_reflection(attribute)
+ if association
+ association.associated_class.make(*args)
+ else
+ raise_argument_error(attribute)
+ end
+ end
+
+ def assign_attribute(attribute, value)
+ association = @klass.association_reflection(attribute)
+ if association
+ if association.returns_array?
+ (object.machinist_deferred_associations ||= []) << [association, value]
+ elsif value.new?
+ value.save(:raise_on_failure => true)
+ super
+ end
+ else
+ super
+ end
+ end
+ end
+end
View
1  machinist.gemspec
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
s.require_paths = ["lib"]
s.add_development_dependency "activerecord"
+ s.add_development_dependency "sequel"
s.add_development_dependency "mysql"
s.add_development_dependency "rake"
s.add_development_dependency "rcov"
View
2  spec/active_record_spec.rb
@@ -85,7 +85,7 @@
username { "user_#{sn}" }
end
Post.blueprint do
- author { User.make(:username => "post_author_#{sn}") }
+ author { ActiveRecordEnvironment::User.make(:username => "post_author_#{sn}") }
end
post = Post.make!
post.should be_a(Post)
View
108 spec/sequel_spec.rb
@@ -0,0 +1,108 @@
+require File.dirname(__FILE__) + '/spec_helper'
+require 'support/sequel_environment'
+
+describe Machinist::Sequel do
+ include SequelEnvironment
+
+ before(:each) do
+ empty_database!
+ end
+
+ context "make" do
+ it "returns an unsaved object" do
+ Post.blueprint { }
+ post = Post.make
+ post.should be_a(Post)
+ post.should be_new
+ end
+ end
+
+ context "make!" do
+ it "makes and saves objects" do
+ Post.blueprint { }
+ post = Post.make!
+ post.should be_a(Post)
+ post.should_not be_new
+ end
+
+ it "raises an exception for an invalid object" do
+ User.blueprint { }
+ lambda {
+ User.make!(:username => "")
+ }.should raise_error(Sequel::ValidationFailed)
+ end
+ end
+
+ context "associations support" do
+ it "handles many_to_one associations" do
+ User.blueprint do
+ username { "user_#{sn}" }
+ end
+ Post.blueprint do
+ author
+ end
+ post = Post.make!
+ post.should be_a(Post)
+ post.should_not be_new
+ post.author.should be_a(User)
+ post.author.should_not be_new
+ end
+
+ it "handles one_to_many associations" do
+ Post.blueprint do
+ comments(3)
+ end
+ Comment.blueprint { }
+ post = Post.make!
+ post.should be_a(Post)
+ post.should_not be_new
+ post.should have(3).comments
+ post.comments.each do |comment|
+ comment.should be_a(Comment)
+ comment.should_not be_new
+ end
+ end
+
+ it "handles many_to_many associations" do
+ Post.blueprint do
+ tags(3)
+ end
+ Tag.blueprint do
+ name { "tag_#{sn}" }
+ end
+ post = Post.make!
+ post.should be_a(Post)
+ post.should_not be_new
+ post.should have(3).tags
+ post.tags.each do |tag|
+ tag.should be_a(Tag)
+ tag.should_not be_new
+ end
+ end
+
+ it "handles overriding associations" do
+ User.blueprint do
+ username { "user_#{sn}" }
+ end
+ Post.blueprint do
+ author { SequelEnvironment::User.make(:username => "post_author_#{sn}") }
+ end
+ post = Post.make!
+ post.should be_a(Post)
+ post.should_not be_new
+ post.author.should be_a(User)
+ post.author.should_not be_new
+ post.author.username.should =~ /^post_author_\d+$/
+ end
+ end
+
+ context "error handling" do
+ it "raises an exception for an attribute with no value" do
+ User.blueprint { username }
+ lambda {
+ User.make
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+end
View
33 spec/support/active_record_environment.rb
@@ -34,26 +34,27 @@
end
end
-class User < ActiveRecord::Base
- validates_presence_of :username
- validates_uniqueness_of :username
-end
-class Post < ActiveRecord::Base
- has_many :comments
- belongs_to :author, :class_name => "User"
- has_and_belongs_to_many :tags
-end
+module ActiveRecordEnvironment
-class Comment < ActiveRecord::Base
- belongs_to :post
-end
+ class User < ActiveRecord::Base
+ validates_presence_of :username
+ validates_uniqueness_of :username
+ end
-class Tag < ActiveRecord::Base
- has_and_belongs_to_many :posts
-end
+ class Post < ActiveRecord::Base
+ has_many :comments
+ belongs_to :author, :class_name => "User"
+ has_and_belongs_to_many :tags
+ end
-module ActiveRecordEnvironment
+ class Comment < ActiveRecord::Base
+ belongs_to :post
+ end
+
+ class Tag < ActiveRecord::Base
+ has_and_belongs_to_many :posts
+ end
def empty_database!
[User, Post, Comment].each do |klass|
View
73 spec/support/sequel_environment.rb
@@ -0,0 +1,73 @@
+require 'sequel'
+require 'sequel/extensions/migration'
+require 'machinist/sequel'
+
+DB = Sequel.mysql(:database => 'machinist')
+
+Sequel.migration do
+ change do
+ create_table! :users do
+ primary_key :id
+ String :username
+ end
+
+ create_table! :posts do
+ primary_key :id
+ String :title
+ Integer :author_id
+ String :body, :text => true
+ end
+
+ create_table! :comments do
+ primary_key :id
+ Integer :post_id
+ String :body, :text => true
+ end
+
+ create_table! :tags do
+ primary_key :id
+ String :name
+ end
+
+ create_table! :posts_tags do
+ Integer :post_id
+ Integer :tag_id
+ end
+ end
+end
+
+
+module SequelEnvironment
+
+ class User < Sequel::Model
+ plugin :validation_helpers
+
+ def validate
+ super
+ validates_presence :username
+ validates_unique :username
+ end
+ end
+
+ class Post < Sequel::Model
+ one_to_many :comments
+ many_to_one :author, :class_name => "SequelEnvironment::User"
+ many_to_many :tags
+ end
+
+ class Comment < Sequel::Model
+ many_to_one :post
+ end
+
+ class Tag < Sequel::Model
+ many_to_many :posts
+ end
+
+ def empty_database!
+ [User, Post, Comment].each do |klass|
+ klass.delete
+ klass.clear_blueprints!
+ end
+ end
+
+end
Something went wrong with that request. Please try again.