Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add instance_filters plugin, for adding arbitrary filters when updati…
…ng/destroying the instance This plugin is designed for cases where you would normally have to drop down to the dataset level to get the necessary control, because you only want to delete or update the rows in certain cases based on the current status of the row in the database.
- Loading branch information
1 parent
684232e
commit 0c42c4c
Showing
5 changed files
with
221 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
module Sequel | ||
module Plugins | ||
# This plugin allows you to add filters on a per object basis that | ||
# restrict updating or deleting the object. It's designed for cases | ||
# where you would normally have to drop down to the dataset level | ||
# to get the necessary control, because you only want to delete or | ||
# update the rows in certain cases based on the current status of | ||
# the row in the database. | ||
# | ||
# class Item < Sequel::Model | ||
# plugin :instance_filters | ||
# end | ||
# | ||
# # These are two separate objects that represent the same | ||
# # database row. | ||
# i1 = Item.first(:id=>1, :delete_allowed=>false) | ||
# i2 = Item.first(:id=>1, :delete_allowed=>false) | ||
# | ||
# # Add an instance filter to the object. This filter is in effect | ||
# # until the object is successfully updated or deleted. | ||
# i1.instance_filter(:delete_allowed=>true) | ||
# | ||
# # Attempting to delete the object where the filter doesn't | ||
# # match any rows raises an error. | ||
# i1.delete # raises Sequel::Error | ||
# | ||
# # The other object that represents the same row has no | ||
# # instance filters, and can be updated normally. | ||
# i2.update(:delete_allowed=>true) | ||
# | ||
# # Even though the filter is now still in effect, since the | ||
# # database row has been updated to allow deleting, | ||
# # delete now works. | ||
# i1.delete | ||
module InstanceFilters | ||
# Exception class raised when updating or deleting an object does | ||
# not affect exactly one row. | ||
class Error < Sequel::Error | ||
end | ||
|
||
module InstanceMethods | ||
# Clear the instance filters after successfully destroying the object. | ||
def after_destroy | ||
super | ||
clear_instance_filters | ||
end | ||
|
||
# Clear the instance filters after successfully updating the object. | ||
def after_update | ||
super | ||
clear_instance_filters | ||
end | ||
|
||
# Add an instance filter to the array of instance filters | ||
# Both the arguments given and the block are passed to the | ||
# dataset's filter method. | ||
def instance_filter(*args, &block) | ||
instance_filters << [args, block] | ||
end | ||
|
||
private | ||
|
||
# Lazily initialize the instance filter array. | ||
def instance_filters | ||
@instance_filters ||= [] | ||
end | ||
|
||
# Apply the instance filters to the given dataset | ||
def apply_instance_filters(ds) | ||
instance_filters.inject(ds){|ds, i| ds.filter(*i[0], &i[1])} | ||
end | ||
|
||
# Clear the instance filters. | ||
def clear_instance_filters | ||
instance_filters.clear | ||
end | ||
|
||
# Apply the instance filters to the dataset returned by super. | ||
def _delete_dataset | ||
apply_instance_filters(super) | ||
end | ||
|
||
# Apply the instance filters to the dataset returned by super. | ||
def _update_dataset | ||
apply_instance_filters(super) | ||
end | ||
|
||
# Raise an Error if calling deleting doesn't | ||
# indicate that a single row was deleted. | ||
def _delete | ||
raise(Error, "No matching object for instance filtered dataset (SQL: #{_delete_dataset.delete_sql})") if super != 1 | ||
end | ||
|
||
# Raise an Error if updating doesn't indicate that a single | ||
# row was updated. | ||
def _update(columns) | ||
raise(Error, "No matching object for instance filtered dataset (SQL: #{_update_dataset.update_sql(columns)})") if super != 1 | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
require File.join(File.dirname(__FILE__), "spec_helper") | ||
|
||
describe "instance_filters plugin" do | ||
before do | ||
@c = Class.new(Sequel::Model(:people)) do | ||
end | ||
@sql = sql = '' | ||
@v = v = [1] | ||
@c.dataset.quote_identifiers = false | ||
@c.dataset.meta_def(:update) do |opts| | ||
sql.replace(update_sql(opts)) | ||
return v.first | ||
end | ||
@c.dataset.meta_def(:delete) do | ||
sql.replace(delete_sql) | ||
return v.first | ||
end | ||
@c.columns :id, :name, :num | ||
@c.plugin :instance_filters | ||
@p = @c.load(:id=>1, :name=>'John', :num=>1) | ||
end | ||
|
||
specify "should raise an error when updating a stale record" do | ||
@p.update(:name=>'Bob') | ||
@sql.should == "UPDATE people SET name = 'Bob' WHERE (id = 1)" | ||
@p.instance_filter(:name=>'Jim') | ||
@v.replace([0]) | ||
proc{@p.update(:name=>'Joe')}.should raise_error(Sequel::Plugins::InstanceFilters::Error) | ||
@sql.should == "UPDATE people SET name = 'Joe' WHERE ((id = 1) AND (name = 'Jim'))" | ||
end | ||
|
||
specify "should raise an error when destroying a stale record" do | ||
@p.destroy | ||
@sql.should == "DELETE FROM people WHERE (id = 1)" | ||
@p.instance_filter(:name=>'Jim') | ||
@v.replace([0]) | ||
proc{@p.destroy}.should raise_error(Sequel::Plugins::InstanceFilters::Error) | ||
@sql.should == "DELETE FROM people WHERE ((id = 1) AND (name = 'Jim'))" | ||
end | ||
|
||
specify "should apply all instance filters" do | ||
@p.instance_filter(:name=>'Jim') | ||
@p.instance_filter{num > 2} | ||
@p.update(:name=>'Bob') | ||
@sql.should == "UPDATE people SET name = 'Bob' WHERE ((id = 1) AND (name = 'Jim') AND (num > 2))" | ||
end | ||
|
||
specify "should drop instance filters after updating" do | ||
@p.instance_filter(:name=>'Joe') | ||
@p.update(:name=>'Joe') | ||
@sql.should == "UPDATE people SET name = 'Joe' WHERE ((id = 1) AND (name = 'Joe'))" | ||
@p.update(:name=>'Bob') | ||
@sql.should == "UPDATE people SET name = 'Bob' WHERE (id = 1)" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters