Permalink
Browse files

beginning of gem structure

  • Loading branch information...
1 parent 73cbdaa commit a69c43b439b15fce47e2707b0702a8cae377a382 @jackdempsey committed Dec 4, 2008
View
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Jack Dempsey
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
112 README
@@ -0,0 +1,112 @@
+Sequel Polymorphic
+==================
+
+A simple plugin for Sequel::Model's that lets you easily create polymorphic associations.
+
+ActiveRecord Style
+------------------
+
+class Asset < ActiveRecord::Base
+ belongs_to :attachable, :polymorphic => true
+end
+
+class Post < ActiveRecord::Base
+ has_many :assets, :as => :attachable
+end
+
+class Note < ActiveRecord::Base
+ has_many :assets, :as => :attachable
+end
+
+@asset.attachable = @post
+@asset.attachable = @note
+
+In Sequel you would do the following:
+
+class Asset < Sequel::Model
+ many_to_one :attachable, :reciprocal=>:assets, \
+ :dataset=>(proc do
+ klass = attachable_type.constantize
+ klass.filter(klass.primary_key=>attachable_id)
+ end), \
+ :eager_loader=>(proc do |key_hash, assets, associations|
+ id_map = {}
+ assets.each do |asset|
+ asset.associations[:attachable] = nil
+ ((id_map[asset.attachable_type] ||= {})[asset.attachable_id] ||= []) << asset
+ end
+ id_map.each do |klass_name, id_map|
+ klass = klass_name.constantize
+ klass.filter(klass.primary_key=>id_map.keys).all do |attach|
+ id_map[attach.pk].each do |asset|
+ asset.associations[:attachable] = attach
+ end
+ end
+ end
+ end)
+
+ private
+
+ def _attachable=(attachable)
+ self[:attachable_id] = (attachable.pk if attachable)
+ self[:attachable_type] = (attachable.class.name if attachable)
+ end
+end
+
+class Post < Sequel::Model
+ one_to_many :assets, :key=>:attachable_id do |ds|
+ ds.filter(:attachable_type=>'Post')
+ end
+
+ private
+
+ def _add_asset(asset)
+ asset.attachable_id = pk
+ asset.attachable_type = 'Post'
+ asset.save
+ end
+ def _remove_asset(asset)
+ asset.attachable_id = nil
+ asset.attachable_type = nil
+ asset.save
+ end
+ def _remove_all_assets
+ Asset.filter(:attachable_id=>pk, :attachable_type=>'Post')\
+ .update(:attachable_id=>nil, :attachable_type=>nil)
+ end
+end
+
+class Note < Sequel::Model
+ one_to_many :assets, :key=>:attachable_id do |ds|
+ ds.filter(:attachable_type=>'Note')
+ end
+
+ private
+
+ def _add_asset(asset)
+ asset.attachable_id = pk
+ asset.attachable_type = 'Note'
+ asset.save
+ end
+ def _remove_asset(asset)
+ asset.attachable_id = nil
+ asset.attachable_type = nil
+ asset.save
+ end
+ def _remove_all_assets
+ Asset.filter(:attachable_id=>pk, :attachable_type=>'Note')\
+ .update(:attachable_id=>nil, :attachable_type=>nil)
+ end
+end
+
+@asset.attachable = @post
+@asset.attachable = @note
+
+
+Thats quite a bit of code. With sequel_polymorphic you can now do:
+
+class Note < Sequel::Model
+ is :polymorphic, :as => :attachable
+end
+
+voila!
View
@@ -0,0 +1,57 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'rubygems/specification'
+require 'date'
+require 'spec/rake/spectask'
+
+GEM = "sequel_polymorphic"
+GEM_VERSION = "0.0.1"
+AUTHOR = "Jack Dempsey"
+EMAIL = "jack.dempsey@gmail.com"
+HOMEPAGE = "http://jackndempsey.blogspot.com"
+SUMMARY = "A gem that provides Sequel::Models with polymorphic association capabilities"
+
+spec = Gem::Specification.new do |s|
+ s.name = GEM
+ s.version = GEM_VERSION
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README.markdown", "LICENSE", 'TODO']
+ s.summary = SUMMARY
+ s.description = s.summary
+ s.author = AUTHOR
+ s.email = EMAIL
+ s.homepage = HOMEPAGE
+
+ # Uncomment this to add a dependency
+ # s.add_dependency "foo"
+
+ s.require_path = 'lib'
+ s.autorequire = GEM
+ s.files = %w(LICENSE README.markdown Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
+end
+
+task :default => :spec
+
+desc "Run specs"
+Spec::Rake::SpecTask.new do |t|
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ t.spec_opts = %w(-fs --color)
+end
+
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+desc "install the gem locally"
+task :install => [:package] do
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
+end
+
+desc "create a gemspec file"
+task :make_spec do
+ File.open("#{GEM}.gemspec", "w") do |file|
+ file.puts spec.to_ruby
+ end
+end
View
0 TODO
No changes.
@@ -0,0 +1,4 @@
+$:.unshift(File.dirname(__FILE__)) unless
+$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
+
+require 'sequel_polymorphic_associations/sequel_polymorphic_associations'
@@ -0,0 +1,16 @@
+module Sequel
+ module Plugins
+ module Polymorphic
+ # Apply the plugin to the model.
+ def self.apply(model, options = {})
+ end
+
+ module InstanceMethods
+ end
+
+ module ClassMethods
+ end # ClassMethods
+ end # Polymorphic
+ end # Plugins
+end # Sequel
+
View
@@ -0,0 +1,18 @@
+DB = Sequel.sqlite
+
+require File.dirname(__FILE__) + '/../lib/sequel_taggable/migration'
+CreateTags.apply(DB, :up)
+
+#DB["select * from sqlite_master"].print
+
+
+class TaggedModel < Sequel::Model
+ set_schema do
+ primary_key :id
+ varchar :name
+ end
+
+ is :taggable
+end
+
+TaggedModel.create_table!
@@ -0,0 +1,57 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+
+describe Sequel::Plugins::Taggable do
+ before do
+ @tagged_model = TaggedModel.new
+ end
+
+ it "should add a .has_tags method to models which include DataMapper::Resource" do
+ TaggedModel.should respond_to(:has_tags)
+ # AnotherTaggedModel.should respond_to(:has_tags)
+ # DefaultTaggedModel.should respond_to(:has_tags)
+ # UntaggedModel.should respond_to(:has_tags)
+ end
+
+ it "should add a .has_tags_on method to models which include DataMapper::Resource" do
+ TaggedModel.should respond_to(:has_tags_on)
+ # AnotherTaggedModel.should respond_to(:has_tags_on)
+ # DefaultTaggedModel.should respond_to(:has_tags_on)
+ # UntaggedModel.should respond_to(:has_tags_on)
+ end
+
+ describe ".has_tags_on" do
+ it "should accept an array of context names" do
+ class HasTagsOnTestModel < Sequel::Model
+ is :taggable
+ end
+ lambda{HasTagsOnTestModel.has_tags_on(:should, 'not', :raise)}.should_not raise_error(ArgumentError)
+ end
+
+ it "should create taggable functionality for each of the context names passed" do
+ class TestModel < Sequel::Model
+ is :taggable
+ has_tags_on :pets, 'skills', :tags
+ end
+ TestModel.should be_taggable
+ a = TestModel.new
+ a.should be_taggable
+ a.should respond_to(:pet_list)
+ a.should respond_to(:skill_list)
+ a.should respond_to(:tag_list)
+ a.should respond_to(:pet_list=)
+ a.should respond_to(:skill_list=)
+ a.should respond_to(:tag_list=)
+ end
+ end
+
+ describe ".has_tags" do
+ it "should raise an error message if someone uses has_tags with an argument list" do
+ lambda do
+ class TagsOnly < Sequel::Model
+ is :taggable
+ has_tags :pets, :skills
+ end
+ end.should raise_error(RuntimeError)
+ end
+ end
+end
@@ -0,0 +1,22 @@
+require File.dirname(__FILE__) + '/../spec_helper.rb'
+
+describe Tag do
+ before(:each) do
+ @tag = Tag.new
+ end
+
+ it "should have id and name properties" do
+ @tag.should respond_to(:id)
+ @tag.should respond_to(:name)
+ end
+
+ it "should have many Taggings" do
+ Tag.associations.should include(:taggings)
+ end
+
+ it "should validate the presence of name" do
+ @tag.should_not be_valid
+ @tag.name = "Meme"
+ @tag.should be_valid
+ end
+end
@@ -0,0 +1,41 @@
+require File.dirname(__FILE__) + '/../spec_helper.rb'
+
+describe Tagging do
+ before(:each) do
+ @tagging = Tagging.new
+ end
+
+ it "should have properties: id, tag_id, taggable_id, taggable_type, tagger_id, tagger_type, and tag_context" do
+ @tagging.should respond_to(:id)
+ @tagging.should respond_to(:tag_id)
+ @tagging.should respond_to(:taggable_id)
+ @tagging.should respond_to(:taggable_type)
+ @tagging.should respond_to(:tag_context)
+ end
+
+ it "should validate the presence of tag_id, taggable_id, taggable_type and tag_context" do
+ @tagging.should_not be_valid
+ @tagging.tag_id = 1
+ @tagging.should_not be_valid
+ @tagging.taggable_id = 1
+ @tagging.should_not be_valid
+ @tagging.taggable_type = "TaggedModel"
+ @tagging.should_not be_valid
+ @tagging.tag_context = "skills"
+ @tagging.should be_valid
+ end
+
+ it "should many_to_one :tag" do
+ Tagging.associations.should include(:tag)
+ Tagging.association_reflection(:tag)[:class_name].should == "Tag"
+ end
+
+ it "should have a method Tagging#taggable which returns the associated taggable instance" do
+ @tagging.should respond_to(:taggable)
+ @tagging.taggable.should_not be
+ @tagging.taggable_id = 11111
+ @tagging.taggable_type = "TaggedModel"
+ TaggedModel.should_receive(:get!).with(11111)
+ @tagging.taggable
+ end
+end
View
@@ -0,0 +1 @@
+--color
View
@@ -0,0 +1,12 @@
+$TESTING=true
+$:.push File.join(File.dirname(__FILE__), '..', 'lib')
+
+require 'rubygems'
+require 'spec'
+require 'sequel'
+
+require File.dirname(__FILE__) + '/sequel-setup'
+require File.dirname(__FILE__) + '/../lib/sequel_taggable'
+
+
+

0 comments on commit a69c43b

Please sign in to comment.