Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move caching, hook class methods, and STI into plugins, deprecate old…
… methods of using them This large commit was necessary because caching and STI both depended on the hook class methods. This commit refactors the plugin support to try loading plugins from sequel/plugins/plugin_name before sequel_plugin_name. It also makes the plugins code use Model::ClassMethods. This commit moves Sequel::Model constants and instance variables to sequel_model.rb, so they will be loaded before other files. This commit refactors Model. inherited because subclass.superclass == self, and to remove the hook class method and STI stuff. This commit removes the PRIVATE_HOOKS, before_update_values and before_delete. They are not needed as with the recent commits, you can just override update_values and delete in the InstanceMethods plugin submodule, do the hook stuff and then call super. Finally, this commit adds a spec_plugin rake task. It's not possible to run the specs for the plugins/extensions in the same task as the model/core specs, because the plugin specs may modify the workings of model/core, and those changes can't be a factor when running the model/core specs. All plugins and extensions are loaded and tested simultaneously, in order to make sure they all work together (currently fairly easy, as they all were available by default).
- Loading branch information
1 parent
d8bb2d6
commit 6f31723
Showing
23 changed files
with
1,423 additions
and
384 deletions.
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,121 @@ | ||
module Sequel | ||
module Plugins | ||
# Sequel's built-in caching plugin supports caching to any object that | ||
# implements the Ruby-Memcache API. You can add caching for any model | ||
# or for all models via: | ||
# | ||
# Model.plugin :caching, store # Cache all models | ||
# MyModel.plugin :caching, store # Just cache MyModel | ||
# | ||
# The cache store should implement the Ruby-Memcache API: | ||
# | ||
# cache_store.set(key, obj, time) # Associate the obj with the given key | ||
# # in the cache for the time (specified | ||
# # in seconds) | ||
# cache_store.get(key) => obj # Returns object set with same key | ||
# cache_store.get(key2) => nil # nil returned if there isn't an object | ||
# # currently in the cache with that key | ||
module Caching | ||
# Set the cache_store and cache_ttl attributes for the given model. | ||
# If the :ttl option is not given, 3600 seconds is the default. | ||
def self.apply(model, store, opts={}) | ||
model.instance_eval do | ||
@cache_store = store | ||
@cache_ttl = opts[:ttl] || 3600 | ||
end | ||
end | ||
|
||
module ClassMethods | ||
# The cache store object for the model, which should implement the | ||
# Ruby-Memcache API | ||
attr_reader :cache_store | ||
|
||
# The time to live for the cache store, in seconds. | ||
attr_reader :cache_ttl | ||
|
||
# Set the time to live for the cache store, in seconds (default is 3600, # so 1 hour). | ||
def set_cache_ttl(ttl) | ||
@cache_ttl = ttl | ||
end | ||
|
||
# Copy the cache_store and cache_ttl to the subclass. | ||
def inherited(subclass) | ||
super | ||
store = @cache_store | ||
ttl = @cache_ttl | ||
subclass.instance_eval do | ||
@cache_store = store | ||
@cache_ttl = ttl | ||
end | ||
end | ||
|
||
private | ||
|
||
# Delete the entry with the matching key from the cache | ||
def cache_delete(key) | ||
@cache_store.delete(key) | ||
nil | ||
end | ||
|
||
# Return a key string for the pk | ||
def cache_key(pk) | ||
"#{self}:#{Array(pk).join(',')}" | ||
end | ||
|
||
# Lookup the primary key in the cache. | ||
# If found, return the matching object. | ||
# Otherwise, get the matching object from the database and | ||
# update the cache with it. | ||
def cache_lookup(pk) | ||
ck = cache_key(pk) | ||
unless obj = @cache_store.get(ck) | ||
obj = dataset[primary_key_hash(pk)] | ||
@cache_store.set(ck, obj, @cache_ttl) | ||
end | ||
obj | ||
end | ||
end | ||
|
||
module InstanceMethods | ||
# Remove the object from the cache when updating | ||
def before_update | ||
return false if super == false | ||
cache_delete | ||
end | ||
|
||
# Return a key unique to the underlying record for caching, based on the | ||
# primary key value(s) for the object. If the model does not have a primary | ||
# key, raise an Error. | ||
def cache_key | ||
raise(Error, "No primary key is associated with this model") unless key = primary_key | ||
pk = case key | ||
when Array | ||
key.collect{|k| @values[k]} | ||
else | ||
@values[key] || (raise Error, 'no primary key for this record') | ||
end | ||
model.send(:cache_key, pk) | ||
end | ||
|
||
# Remove the object from the cache when deleting | ||
def delete | ||
cache_delete | ||
super | ||
end | ||
|
||
# Remove the object from the cache when updating | ||
def update_values(*args) | ||
cache_delete | ||
super | ||
end | ||
|
||
private | ||
|
||
# Delete this object from the cache | ||
def cache_delete | ||
model.send(:cache_delete, cache_key) | ||
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,17 @@ | ||
module Sequel | ||
module Plugins | ||
module HookClassMethods | ||
module ClassMethods | ||
Model::HOOKS.each{|h| class_eval("def #{h}(method = nil, &block); add_hook(:#{h}, method, &block) end", __FILE__, __LINE__)} | ||
|
||
def add_hook_type(*hooks) | ||
hooks.each do |hook| | ||
@hooks[hook] = [] | ||
instance_eval("def #{hook}(method = nil, &block); add_hook(:#{hook}, method, &block) end", __FILE__, __LINE__) | ||
class_eval("def #{hook}; run_hooks(:#{hook}); end", __FILE__, __LINE__) | ||
end | ||
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,62 @@ | ||
module Sequel | ||
module Plugins | ||
# Sequel's built in Single Table Inheritance plugin makes subclasses | ||
# of this model only load rows where the given key field matches the | ||
# subclass's name. If the key given has a NULL value or there are | ||
# any problems looking up the class, uses the current class. | ||
# | ||
# You should only use this in the parent class, not in the subclasses. | ||
# | ||
# You shouldn't call set_dataset in the model after applying this | ||
# plugin, otherwise subclasses might use the wrong dataset. | ||
# | ||
# The filters and row_proc that sti_key sets up in subclasses may not work correctly if | ||
# those subclasses have further subclasses. For those middle subclasses, | ||
# you may need to call set_dataset manually with the correct filter and | ||
# row_proc. | ||
module SingleTableInheritance | ||
# Set the sti_key and sti_dataset for the model, and change the | ||
# dataset's row_proc so that the dataset yields objects of varying classes, | ||
# where the class used has the same name as the key field. | ||
def self.apply(model, key) | ||
model.instance_eval do | ||
@sti_key = key | ||
@sti_dataset = dataset | ||
dataset.row_proc = Proc.new{|r| (r[key].constantize rescue model).load(r)} | ||
end | ||
end | ||
|
||
module ClassMethods | ||
# The base dataset for STI, to which filters are added to get | ||
# only the models for the specific STI subclass. | ||
attr_reader :sti_dataset | ||
|
||
# The column name holding the STI key for this model | ||
attr_reader :sti_key | ||
|
||
# Copy the sti_key and sti_dataset to the subclasses, and filter the | ||
# subclass's dataset so it is restricted to rows where the key column | ||
# matches the subclass's name. | ||
def inherited(subclass) | ||
super | ||
sk = sti_key | ||
sd = sti_dataset | ||
subclass.set_dataset(sd.filter(sk=>subclass.name.to_s), :inherited=>true) | ||
subclass.instance_eval do | ||
@sti_key = sk | ||
@sti_dataset = sd | ||
@simple_table = nil | ||
end | ||
end | ||
end | ||
|
||
module InstanceMethods | ||
# Set the sti_key column to the name of the model. | ||
def before_create | ||
return false if super == false | ||
send("#{model.sti_key}=", model.name.to_s) | ||
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
Oops, something went wrong.