Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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...
commit 6f317238af1571125d4e6c2326c5cef60ed38f07 1 parent d8bb2d6
@jeremyevans authored
View
8 Rakefile
@@ -98,6 +98,7 @@ lib_dir = File.join(File.dirname(__FILE__), 'lib')
fixRUBYLIB = Proc.new{ENV['RUBYLIB'] ? (ENV['RUBYLIB'] += ":#{lib_dir}") : (ENV['RUBYLIB'] = lib_dir)}
sequel_core_specs = "spec/sequel_core/*_spec.rb"
sequel_model_specs = "spec/sequel_model/*_spec.rb"
+sequel_plugin_specs = "spec/extensions/*_spec.rb"
spec_opts = proc{File.read("spec/spec.opts").split("\n")}
rcov_opts = proc{File.read("spec/rcov.opts").split("\n")}
@@ -132,6 +133,13 @@ Spec::Rake::SpecTask.new("spec_model") do |t|
t.spec_opts = spec_opts.call
end
+desc "Run extension/plugin specs"
+Spec::Rake::SpecTask.new("spec_plugin") do |t|
+ fixRUBYLIB.call
+ t.spec_files = FileList[sequel_plugin_specs]
+ t.spec_opts = spec_opts.call
+end
+
desc "Run integration tests"
Spec::Rake::SpecTask.new("integration") do |t|
fixRUBYLIB.call
View
121 lib/sequel/plugins/caching.rb
@@ -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
View
17 lib/sequel/plugins/hook_class_methods.rb
@@ -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
View
62 lib/sequel/plugins/single_table_inheritance.rb
@@ -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
View
58 lib/sequel_model.rb
@@ -43,9 +43,8 @@ def self.Model(source)
# * The following instance_methods all call the class method of the same
# name: columns, dataset, db, primary_key, db_schema.
# * The following class level attr_readers are created: allowed_columns,
- # cache_store, cache_ttl, dataset_methods, primary_key, restricted_columns,
- # sti_dataset, and sti_key. You should not usually need to
- # access these directly.
+ # dataset_methods, primary_key, and restricted_columns.
+ # You should not usually need to access these directly.
# * All validation methods also accept the options specified in #validates_each,
# in addition to the options specified in the RDoc for that method.
# * The following class level attr_accessors are created: raise_on_typecast_failure,
@@ -81,11 +80,60 @@ def self.Model(source)
# * Model.dataset= => set_dataset
# * Model.is_a => is
class Model
+ # Dataset methods to proxy via metaprogramming
+ DATASET_METHODS = %w'<< all avg count delete distinct eager eager_graph each each_page
+ empty? except exclude filter first from from_self full_outer_join get graph
+ group group_and_count group_by having inner_join insert
+ insert_multiple intersect interval join join_table last
+ left_outer_join limit map multi_insert naked order order_by order_more
+ paginate print query range reverse_order right_outer_join select
+ select_all select_more server set set_graph_aliases single_value to_csv to_hash
+ transform union unfiltered unordered update where with_sql'.map{|x| x.to_sym}
+
+ # Empty instance variables, for -w compliance
+ EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@transform, :@db, :@skip_superclass_validations]
+
+ # Hooks that are safe for public use
+ HOOKS = [:after_initialize, :before_create, :after_create, :before_update,
+ :after_update, :before_save, :after_save, :before_destroy, :after_destroy,
+ :before_validation, :after_validation]
+
+ # Instance variables that are inherited in subclasses
+ INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@dataset_methods=>:dup, :@primary_key=>nil,
+ :@raise_on_save_failure=>nil, :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
+ :@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
+ :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
+ :@raise_on_typecast_failure=>nil, :@association_reflections=>:dup}
+
+ # The setter methods (methods ending with =) that are never allowed
+ # to be called automatically via set.
+ RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure= raise_on_typecast_failure="
+
+ @allowed_columns = nil
+ @association_reflections = {}
+ @cache_store = nil
+ @cache_ttl = nil
+ @db = nil
+ @db_schema = nil
+ @dataset_methods = {}
+ @overridable_methods_module = nil
+ @primary_key = :id
+ @raise_on_save_failure = true
+ @raise_on_typecast_failure = true
+ @restrict_primary_key = true
+ @restricted_columns = nil
+ @simple_pk = nil
+ @simple_table = nil
+ @skip_superclass_validations = nil
+ @strict_param_setting = true
+ @transform = nil
+ @typecast_empty_string_to_nil = true
+ @typecast_on_assignment = true
end
end
-%w"inflector record association_reflection associations base hooks schema dataset_methods
- caching plugins validations eager_loading exceptions deprecated".each do |f|
+%w"inflector plugins record association_reflection associations base hooks schema dataset_methods
+ caching validations eager_loading exceptions deprecated".each do |f|
require "sequel_model/#{f}"
end
View
90 lib/sequel_model/base.rb
@@ -1,52 +1,5 @@
module Sequel
class Model
-
- # Dataset methods to proxy via metaprogramming
- DATASET_METHODS = %w'<< all avg count delete distinct eager eager_graph each each_page
- empty? except exclude filter first from from_self full_outer_join get graph
- group group_and_count group_by having inner_join insert
- insert_multiple intersect interval join join_table last
- left_outer_join limit map multi_insert naked order order_by order_more
- paginate print query range reverse_order right_outer_join select
- select_all select_more server set set_graph_aliases single_value to_csv to_hash
- transform union unfiltered unordered update where with_sql'.map{|x| x.to_sym}
-
- # Instance variables that are inherited in subclasses
- INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@cache_store=>nil,
- :@cache_ttl=>nil, :@dataset_methods=>:dup, :@primary_key=>nil,
- :@raise_on_save_failure=>nil, :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
- :@simple_pk=>nil, :@simple_table=>nil,
- :@sti_dataset=>nil, :@sti_key=>nil, :@strict_param_setting=>nil,
- :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
- :@raise_on_typecast_failure=>nil, :@association_reflections=>:dup}
-
- # Empty instance variables, for -w compliance
- EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@transform, :@db, :@skip_superclass_validations]
-
- @allowed_columns = nil
- @association_reflections = {}
- @cache_store = nil
- @cache_ttl = nil
- @db = nil
- @db_schema = nil
- @dataset_methods = {}
- @hooks = {}
- @overridable_methods_module = nil
- @primary_key = :id
- @raise_on_save_failure = true
- @raise_on_typecast_failure = true
- @restrict_primary_key = true
- @restricted_columns = nil
- @simple_pk = nil
- @simple_table = nil
- @skip_superclass_validations = nil
- @sti_dataset = nil
- @sti_key = nil
- @strict_param_setting = true
- @transform = nil
- @typecast_empty_string_to_nil = true
- @typecast_on_assignment = true
-
module ClassMethods
# Which columns should be the only columns allowed in a call to set
# (default: all columns).
@@ -82,13 +35,6 @@ module ClassMethods
# or nil otherwise.
attr_reader :simple_table
- # 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
-
# Whether new/set/update and their variants should raise an error
# if an invalid key is used (either that doesn't exist or that
# access is restricted to it).
@@ -208,29 +154,26 @@ def find_or_create(cond)
# is created. Also, inherit the INHERITED_INSTANCE_VARIABLES
# from the parent class.
def inherited(subclass)
- sup_class = subclass.superclass
ivs = subclass.instance_variables.collect{|x| x.to_s}
EMPTY_INSTANCE_VARIABLES.each{|iv| subclass.instance_variable_set(iv, nil) unless ivs.include?(iv.to_s)}
INHERITED_INSTANCE_VARIABLES.each do |iv, dup|
next if ivs.include?(iv.to_s)
- sup_class_value = sup_class.instance_variable_get(iv)
+ sup_class_value = instance_variable_get(iv)
sup_class_value = sup_class_value.dup if dup == :dup && sup_class_value
subclass.instance_variable_set(iv, sup_class_value)
end
unless ivs.include?("@dataset")
db
begin
- if sup_class == Model
+ if self == Model
subclass.set_dataset(subclass.implicit_table_name) unless subclass.name.blank?
- elsif ds = sup_class.instance_variable_get(:@dataset)
- subclass.set_dataset(sup_class.sti_key ? sup_class.sti_dataset.filter(sup_class.sti_key=>subclass.name.to_s) : ds.clone, :inherited=>true)
+ elsif ds = instance_variable_get(:@dataset)
+ subclass.set_dataset(ds.clone, :inherited=>true)
end
rescue
nil
end
end
- hooks = subclass.instance_variable_set(:@hooks, {})
- sup_class.instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup}
end
# Returns the implicit table name for the model class.
@@ -339,7 +282,7 @@ def set_dataset(ds, opts={})
@dataset.row_proc = Proc.new{|r| load(r)}
@dataset.transform(@transform) if @transform
if inherited
- @simple_table = sti_key ? nil : superclass.simple_table
+ @simple_table = superclass.simple_table
@columns = @dataset.columns rescue nil
else
@dataset.extend(DatasetMethods)
@@ -383,29 +326,6 @@ def set_restricted_columns(*cols)
@restricted_columns = cols
end
- # Makes this model a polymorphic model with the given key being a string
- # field in the database holding the name of the class to use. If the
- # key given has a NULL value or there are any problems looking up the
- # class, uses the current class.
- #
- # This should be used to set up single table inheritance for the model,
- # and it only makes sense to use this in the parent class.
- #
- # You should call sti_key after any calls to set_dataset in the model,
- # otherwise subclasses might not have the filters set up correctly.
- #
- # The filters and row_proc that sti_key sets up in subclasses will not work if
- # those subclasses have further subclasses. For those middle subclasses,
- # you will need to call set_dataset manually with the correct filter and
- # row_proc.
- def set_sti_key(key)
- m = self
- @sti_key = key
- @sti_dataset = dataset
- dataset.row_proc = Proc.new{|r| (r[key].constantize rescue m).load(r)}
- before_create(:set_sti_key){send("#{key}=", model.name.to_s)}
- end
-
# Defines a method that returns a filtered dataset. Subsets
# create dataset methods, so they can be chained for scoping.
# For example:
View
82 lib/sequel_model/caching.rb
@@ -1,82 +0,0 @@
-module Sequel
- class Model
- metaattr_reader :cache_store, :cache_ttl
-
- ### Public Class Methods ###
-
- # Set the cache store for the model, as well as the caching before_* hooks.
- #
- # The cache store should implement the following 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
- def self.set_cache(store, opts = {})
- @cache_store = store
- @cache_ttl = opts[:ttl] || 3600
- before_update :cache_delete
- before_update_values :cache_delete
- before_delete :cache_delete
- end
-
- # Set the time to live for the cache store, in seconds (default is 3600,
- # so 1 hour).
- def self.set_cache_ttl(ttl)
- @cache_ttl = ttl
- end
-
- ### Private Class Methods ###
-
- # Delete the entry with the matching key from the cache
- def self.cache_delete(key) # :nodoc:
- @cache_store.delete(key)
- nil
- end
-
- # Return a key string for the pk
- def self.cache_key(pk) # :nodoc:
- "#{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 self.cache_lookup(pk) # :nodoc:
- 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
-
- private_class_method :cache_delete, :cache_key, :cache_lookup
-
- ### Instance Methods ###
-
- # 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
-
- private
-
- # Delete this object from the cache
- def cache_delete
- model.send(:cache_delete, cache_key)
- end
- end
-end
View
26 lib/sequel_model/deprecated.rb
@@ -29,6 +29,31 @@ def self.str_columns
@str_columns ||= columns.map{|c| c.to_s.freeze}
end
+ def self.set_sti_key(key)
+ Deprecation.deprecate('Sequel::Model.set_sti_key', 'Use Model.plugin(:single_table_inheritance, key)')
+ plugin :single_table_inheritance, key
+ end
+
+ def self.sti_key
+ Deprecation.deprecate('Sequel::Model.sti_key', 'Use Model.plugin(:single_table_inheritance, key) first')
+ @sti_key
+ end
+
+ def self.sti_dataset
+ Deprecation.deprecate('Sequel::Model.sti_dataset', 'Use Model.plugin(:single_table_inheritance, key) first')
+ @sti_dataset
+ end
+
+ def self.set_cache(store, opts = {})
+ Deprecation.deprecate('Sequel::Model.set_cache', 'Use Model.plugin(:caching, store, opts)')
+ plugin :caching, store, opts
+ end
+
+ def self.set_cache_ttl(ttl)
+ Deprecation.deprecate('Sequel::Model.set_cache_ttl', 'Use Model.plugin(:caching, store, opts) first')
+ @cache_ttl = ttl
+ end
+
def dataset
Deprecation.deprecate('Sequel::Model#dataset', 'Use model_object.model.dataset')
model.dataset
@@ -79,7 +104,6 @@ def set_values(values)
def update_values(values)
Deprecation.deprecate('Sequel::Model#update_values', 'Use Sequel::Model#update or model_object.this.update')
- before_update_values
this.update(set_values(values))
end
View
178 lib/sequel_model/hooks.rb
@@ -1,101 +1,103 @@
module Sequel
- class Model
- # Hooks that are safe for public use
- HOOKS = [:after_initialize, :before_create, :after_create, :before_update,
- :after_update, :before_save, :after_save, :before_destroy, :after_destroy,
- :before_validation, :after_validation]
-
- # Hooks that are only for internal use
- PRIVATE_HOOKS = [:before_update_values, :before_delete]
-
- # This adds a new hook type. It will define both a class
- # method that you can use to add hooks, as well as an instance method
- # that you can use to call all hooks of that type. The class method
- # can be called with a symbol or a block or both. If a block is given and
- # and symbol is not, it adds the hook block to the hook type. If a block
- # and symbol are both given, it replaces the hook block associated with
- # that symbol for a given hook type, or adds it if there is no hook block
- # with that symbol for that hook type. If no block is given, it assumes
- # the symbol specifies an instance method to call and adds it to the hook
- # type.
- #
- # If any hook block returns false, the instance method will return false
- # immediately without running the rest of the hooks of that type.
- #
- # It is recommended that you always provide a symbol to this method,
- # for descriptive purposes. It's only necessary to do so when you
- # are using a system that reloads code.
- #
- # All of Sequel's standard hook types are also implemented using this
- # method.
- #
- # Example of usage:
- #
- # class MyModel
- # define_hook :before_move_to
- # before_move_to(:check_move_allowed){|o| o.allow_move?}
- # def move_to(there)
- # return if before_move_to == false
- # # move MyModel object to there
- # end
- # end
- def self.add_hook_type(*hooks)
- hooks.each do |hook|
- @hooks[hook] = []
- instance_eval("def #{hook}(method = nil, &block); define_hook_instance_method(:#{hook}); add_hook(:#{hook}, method, &block) end")
- class_eval("def #{hook}; end")
+ module Plugins
+ module DeprecatedHookClassMethods
+ def self.apply(model)
+ hooks = model.instance_variable_set(:@hooks, {})
+ Model::HOOKS.each{|h| hooks[h] = []}
end
- end
- # Returns true if there are any hook blocks for the given hook.
- def self.has_hooks?(hook)
- !@hooks[hook].empty?
- end
-
- # Yield every block related to the given hook.
- def self.hook_blocks(hook)
- @hooks[hook].each{|k,v| yield v}
- end
+ module ClassMethods
+ Model::HOOKS.each{|h| class_eval("def #{h}(method = nil, &block); Deprecation.deprecate('Sequel::Model.#{h}', 'Use Model.plugin :hook_class_methods'); add_hook(:#{h}, method, &block) end", __FILE__, __LINE__)}
- ### Private Class Methods ###
+ # This adds a new hook type. It will define both a class
+ # method that you can use to add hooks, as well as an instance method
+ # that you can use to call all hooks of that type. The class method
+ # can be called with a symbol or a block or both. If a block is given and
+ # and symbol is not, it adds the hook block to the hook type. If a block
+ # and symbol are both given, it replaces the hook block associated with
+ # that symbol for a given hook type, or adds it if there is no hook block
+ # with that symbol for that hook type. If no block is given, it assumes
+ # the symbol specifies an instance method to call and adds it to the hook
+ # type.
+ #
+ # If any hook block returns false, the instance method will return false
+ # immediately without running the rest of the hooks of that type.
+ #
+ # It is recommended that you always provide a symbol to this method,
+ # for descriptive purposes. It's only necessary to do so when you
+ # are using a system that reloads code.
+ #
+ # Example of usage:
+ #
+ # class MyModel
+ # define_hook :before_move_to
+ # before_move_to(:check_move_allowed){|o| o.allow_move?}
+ # def move_to(there)
+ # return if before_move_to == false
+ # # move MyModel object to there
+ # end
+ # end
+ def add_hook_type(*hooks)
+ Deprecation.deprecate('Sequel::Model.add_hook_type', 'Use Model.plugin :hook_class_methods')
+ 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
+
+ # Returns true if there are any hook blocks for the given hook.
+ def has_hooks?(hook)
+ !@hooks[hook].empty?
+ end
+
+ # Yield every block related to the given hook.
+ def hook_blocks(hook)
+ @hooks[hook].each{|k,v| yield v}
+ end
- # Add a hook block to the list of hook methods.
- # If a non-nil tag is given and it already is in the list of hooks,
- # replace it with the new block.
- def self.add_hook(hook, tag, &block) #:nodoc:
- unless block
- (raise Error, 'No hook method specified') unless tag
- block = proc {send tag}
- end
- h = @hooks[hook]
- if tag && (old = h.find{|x| x[0] == tag})
- old[1] = block
- else
- if hook.to_s =~ /^before/
- h.unshift([tag,block])
- else
- h << [tag, block]
+ def inherited(subclass)
+ super
+ hooks = subclass.instance_variable_set(:@hooks, {})
+ instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup}
+ end
+
+ private
+
+ # Add a hook block to the list of hook methods.
+ # If a non-nil tag is given and it already is in the list of hooks,
+ # replace it with the new block.
+ def add_hook(hook, tag, &block)
+ unless block
+ (raise Error, 'No hook method specified') unless tag
+ block = proc {send tag}
+ end
+ h = @hooks[hook]
+ if tag && (old = h.find{|x| x[0] == tag})
+ old[1] = block
+ else
+ if hook.to_s =~ /^before/
+ h.unshift([tag,block])
+ else
+ h << [tag, block]
+ end
+ end
end
end
- end
- # Define a hook instance method that calls the run_hooks instance method.
- def self.define_hook_instance_method(hook) #:nodoc:
- class_eval("def #{hook}; run_hooks(:#{hook}); end")
- end
+ module InstanceMethods
+ Model::HOOKS.each{|h| class_eval("def #{h}; run_hooks(:#{h}); end", __FILE__, __LINE__)}
- private_class_method :add_hook, :define_hook_instance_method
+ private
- private
-
- # Runs all hook blocks of given hook type on this object.
- # Stops running hook blocks and returns false if any hook block returns false.
- def run_hooks(hook)
- model.hook_blocks(hook){|block| return false if instance_eval(&block) == false}
+ # Runs all hook blocks of given hook type on this object.
+ # Stops running hook blocks and returns false if any hook block returns false.
+ def run_hooks(hook)
+ model.hook_blocks(hook){|block| return false if instance_eval(&block) == false}
+ end
+ end
end
-
- # For performance reasons, we define empty hook instance methods, which are
- # overwritten with real hook instance methods whenever the hook class method is called.
- add_hook_type(*(HOOKS + PRIVATE_HOOKS))
end
+
+ Model.plugin :deprecated_hook_class_methods
end
View
87 lib/sequel_model/plugins.rb
@@ -1,6 +1,6 @@
module Sequel
# Empty namespace that plugins should use to store themselves,
- # so they can be loaded via Model.is.
+ # so they can be loaded via Model.plugin.
#
# Plugins should be modules with one of the following conditions:
# * A singleton method named apply, which takes a model and
@@ -15,49 +15,58 @@ module Plugins
end
class Model
- # Loads a plugin for use with the model class, passing optional arguments
- # to the plugin. If the plugin has a DatasetMethods module and the model
- # doesn't have a dataset, raise an Error.
- def self.plugin(plugin, *args)
- arg = args.first
- block = lambda{arg}
- m = plugin_module(plugin)
- raise(Error, "Plugin cannot be applied because the model class has no dataset") if m.const_defined?("DatasetMethods") && !@dataset
- if m.respond_to?(:apply)
- m.apply(self, *args)
+ module ClassMethods
+ # Loads a plugin for use with the model class, passing optional arguments
+ # to the plugin. If the plugin has a DatasetMethods module and the model
+ # doesn't have a dataset, raise an Error.
+ def plugin(plugin, *args)
+ arg = args.first
+ block = lambda{arg}
+ m = plugin_module(plugin)
+ raise(Error, "Plugin cannot be applied because the model class has no dataset") if m.const_defined?("DatasetMethods") && !@dataset
+ if m.respond_to?(:apply)
+ m.apply(self, *args)
+ end
+ if m.const_defined?("InstanceMethods")
+ define_method(:"#{plugin}_opts", &block)
+ include(m::InstanceMethods)
+ end
+ if m.const_defined?("ClassMethods")
+ meta_def(:"#{plugin}_opts", &block)
+ extend(m::ClassMethods)
+ end
+ if m.const_defined?("DatasetMethods")
+ dataset.meta_def(:"#{plugin}_opts", &block)
+ dataset.extend(m::DatasetMethods)
+ def_dataset_method(*m::DatasetMethods.public_instance_methods)
+ end
end
- if m.const_defined?("InstanceMethods")
- define_method(:"#{plugin}_opts", &block)
- include(m::InstanceMethods)
- end
- if m.const_defined?("ClassMethods")
- meta_def(:"#{plugin}_opts", &block)
- extend(m::ClassMethods)
+
+ private
+
+ # Returns the new style location for the plugin name.
+ def plugin_gem_location(plugin)
+ "sequel/plugins/#{plugin}"
end
- if m.const_defined?("DatasetMethods")
- dataset.meta_def(:"#{plugin}_opts", &block)
- dataset.extend(m::DatasetMethods)
- def_dataset_method(*m::DatasetMethods.public_instance_methods)
+
+ # Returns the old style location for the plugin name.
+ def plugin_gem_location_old(plugin)
+ "sequel_#{plugin}"
end
- end
- ### Private Class Methods ###
-
- # Returns the gem name for the given plugin.
- def self.plugin_gem(plugin) # :nodoc:
- "sequel_#{plugin}"
- end
-
- # Returns the module for the specified plugin. If the module is not
- # defined, the corresponding plugin gem is automatically loaded.
- def self.plugin_module(plugin) # :nodoc:
- module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
- if not Sequel::Plugins.const_defined?(module_name)
- require plugin_gem(plugin)
+ # Returns the module for the specified plugin. If the module is not
+ # defined, the corresponding plugin gem is automatically loaded.
+ def plugin_module(plugin)
+ module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
+ if not Sequel::Plugins.const_defined?(module_name)
+ begin
+ require plugin_gem_location(plugin)
+ rescue LoadError
+ require plugin_gem_location_old(plugin)
+ end
+ end
+ Sequel::Plugins.const_get(module_name)
end
- Sequel::Plugins.const_get(module_name)
end
-
- private_class_method :plugin_gem, :plugin_module
end
end
View
7 lib/sequel_model/record.rb
@@ -1,10 +1,8 @@
module Sequel
class Model
- # The setter methods (methods ending with =) that are never allowed
- # to be called automatically via set.
- RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure= raise_on_typecast_failure="
-
module InstanceMethods
+ HOOKS.each{|h| class_eval("def #{h}; end", __FILE__, __LINE__)}
+
# Define instance method(s) that calls class method(s) of the
# same name, caching the result in an instance variable. Define
# standard attr_writer method for modifying that instance variable
@@ -107,7 +105,6 @@ def changed_columns
# Deletes and returns self. Does not run destroy hooks.
# Look into using destroy instead.
def delete
- before_delete
this.delete
self
end
View
201 spec/extensions/caching_spec.rb
@@ -0,0 +1,201 @@
+require File.join(File.dirname(__FILE__), "spec_helper")
+
+describe Sequel::Model, "caching" do
+ before do
+ MODEL_DB.reset
+
+ @cache_class = Class.new(Hash) do
+ attr_accessor :ttl
+ def set(k, v, ttl); self[k] = v; @ttl = ttl; end
+ def get(k); self[k]; end
+ end
+ cache = @cache_class.new
+ @cache = cache
+
+ @c = Class.new(Sequel::Model(:items))
+ @c.class_eval do
+ plugin :caching, cache
+ def self.name; 'Item' end
+
+ columns :name, :id
+ end
+
+ $cache_dataset_row = {:name => 'sharon', :id => 1}
+ @dataset = @c.dataset
+ $sqls = []
+ @dataset.extend(Module.new {
+ def fetch_rows(sql)
+ $sqls << sql
+ yield $cache_dataset_row
+ end
+
+ def update(values)
+ $sqls << update_sql(values)
+ $cache_dataset_row.merge!(values)
+ end
+
+ def delete
+ $sqls << delete_sql
+ end
+ })
+ @c2 = Class.new(@c) do
+ def self.name; 'SubItem' end
+ end
+ end
+
+ it "should set the model's cache store" do
+ @c.cache_store.should be(@cache)
+ @c2.cache_store.should be(@cache)
+ end
+
+ it "should have a default ttl of 3600" do
+ @c.cache_ttl.should == 3600
+ @c2.cache_ttl.should == 3600
+ end
+
+ it "should take a ttl option" do
+ @c.plugin :caching, @cache, :ttl => 1234
+ @c.cache_ttl.should == 1234
+ Class.new(@c).cache_ttl.should == 1234
+ end
+
+ it "should offer a set_cache_ttl method for setting the ttl" do
+ @c.cache_ttl.should == 3600
+ @c.set_cache_ttl 1234
+ @c.cache_ttl.should == 1234
+ Class.new(@c).cache_ttl.should == 1234
+ end
+
+ it "should generate a cache key appropriate to the class" do
+ m = @c.new
+ m.values[:id] = 1
+ m.cache_key.should == "#{m.class}:1"
+ m = @c2.new
+ m.values[:id] = 1
+ m.cache_key.should == "#{m.class}:1"
+
+ # custom primary key
+ @c.set_primary_key :ttt
+ m = @c.new
+ m.values[:ttt] = 333
+ m.cache_key.should == "#{m.class}:333"
+ c = Class.new(@c)
+ m = c.new
+ m.values[:ttt] = 333
+ m.cache_key.should == "#{m.class}:333"
+
+ # composite primary key
+ @c.set_primary_key [:a, :b, :c]
+ m = @c.new
+ m.values[:a] = 123
+ m.values[:c] = 456
+ m.values[:b] = 789
+ m.cache_key.should == "#{m.class}:123,789,456"
+ c = Class.new(@c)
+ m = c.new
+ m.values[:a] = 123
+ m.values[:c] = 456
+ m.values[:b] = 789
+ m.cache_key.should == "#{m.class}:123,789,456"
+ end
+
+ it "should raise error if attempting to generate cache_key and primary key value is null" do
+ m = @c.new
+ proc {m.cache_key}.should raise_error(Sequel::Error)
+ m.values[:id] = 1
+ proc {m.cache_key}.should_not raise_error(Sequel::Error)
+
+ m = @c2.new
+ proc {m.cache_key}.should raise_error(Sequel::Error)
+ m.values[:id] = 1
+ proc {m.cache_key}.should_not raise_error(Sequel::Error)
+ end
+
+ it "should not raise error if trying to save a new record" do
+ proc {@c.new(:name=>'blah').save}.should_not raise_error
+ proc {@c.create(:name=>'blah')}.should_not raise_error
+ proc {@c2.new(:name=>'blah').save}.should_not raise_error
+ proc {@c2.create(:name=>'blah')}.should_not raise_error
+ end
+
+ it "should set the cache when reading from the database" do
+ $sqls.should == []
+ @cache.should be_empty
+
+ m = @c[1]
+ $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
+ m.values.should == $cache_dataset_row
+ @cache[m.cache_key].should == m
+ m2 = @c[1]
+ $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
+ m2.should == m
+ m2.values.should == $cache_dataset_row
+
+ $sqls.clear
+ m = @c2[1]
+ $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
+ m.values.should == $cache_dataset_row
+ @cache[m.cache_key].should == m
+ m2 = @c2[1]
+ $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
+ m2.should == m
+ m2.values.should == $cache_dataset_row
+ end
+
+ it "should delete the cache when writing to the database" do
+ m = @c[1]
+ @cache[m.cache_key].should == m
+ m.name = 'hey'
+ m.save
+ @cache.has_key?(m.cache_key).should be_false
+ $sqls.last.should == "UPDATE items SET name = 'hey', id = 1 WHERE (id = 1)"
+
+ m = @c2[1]
+ @cache[m.cache_key].should == m
+ m.name = 'hey'
+ m.save
+ @cache.has_key?(m.cache_key).should be_false
+ $sqls.last.should == "UPDATE items SET name = 'hey', id = 1 WHERE (id = 1)"
+ end
+
+ it "should delete the cache when deleting the record" do
+ m = @c[1]
+ @cache[m.cache_key].should == m
+ m.delete
+ @cache.has_key?(m.cache_key).should be_false
+ $sqls.last.should == "DELETE FROM items WHERE (id = 1)"
+
+ m = @c2[1]
+ @cache[m.cache_key].should == m
+ m.delete
+ @cache.has_key?(m.cache_key).should be_false
+ $sqls.last.should == "DELETE FROM items WHERE (id = 1)"
+ end
+
+ it "should support #[] as a shortcut to #find with hash" do
+ m = @c[:id => 3]
+ @cache[m.cache_key].should be_nil
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 3) LIMIT 1"
+ m = @c[1]
+ @cache[m.cache_key].should == m
+ $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
+ @c[:id => 4]
+ $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1", \
+ "SELECT * FROM items WHERE (id = 4) LIMIT 1"]
+
+ $sqls.clear
+ m = @c2[:id => 3]
+ @cache[m.cache_key].should be_nil
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 3) LIMIT 1"
+ m = @c2[1]
+ @cache[m.cache_key].should == m
+ $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
+ @c2[:id => 4]
+ $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1", \
+ "SELECT * FROM items WHERE (id = 4) LIMIT 1"]
+ end
+end
View
470 spec/extensions/hook_class_methods_spec.rb
@@ -0,0 +1,470 @@
+require File.join(File.dirname(__FILE__), "spec_helper")
+
+describe "Model hooks" do
+ before do
+ MODEL_DB.reset
+ end
+
+ specify "should be definable using a block" do
+ $adds = []
+ c = Class.new(Sequel::Model)
+ c.class_eval do
+ before_save {$adds << 'hi'}
+ end
+
+ c.new.before_save
+ $adds.should == ['hi']
+ end
+
+ specify "should be definable using a method name" do
+ $adds = []
+ c = Class.new(Sequel::Model)
+ c.class_eval do
+ def bye; $adds << 'bye'; end
+ before_save :bye
+ end
+
+ c.new.before_save
+ $adds.should == ['bye']
+ end
+
+ specify "should be additive" do
+ $adds = []
+ c = Class.new(Sequel::Model)
+ c.class_eval do
+ after_save {$adds << 'hyiyie'}
+ after_save {$adds << 'byiyie'}
+ end
+
+ c.new.after_save
+ $adds.should == ['hyiyie', 'byiyie']
+ end
+
+ specify "before hooks should run in reverse order" do
+ $adds = []
+ c = Class.new(Sequel::Model)
+ c.class_eval do
+ before_save {$adds << 'hyiyie'}
+ before_save {$adds << 'byiyie'}
+ end
+
+ c.new.before_save
+ $adds.should == ['byiyie', 'hyiyie']
+ end
+
+ specify "should not be additive if the method or tag already exists" do
+ $adds = []
+ c = Class.new(Sequel::Model)
+ c.class_eval do
+ def bye; $adds << 'bye'; end
+ before_save :bye
+ before_save :bye
+ end
+
+ c.new.before_save
+ $adds.should == ['bye']
+
+ $adds = []
+ d = Class.new(Sequel::Model)
+ d.class_eval do
+ before_save(:bye){$adds << 'hyiyie'}
+ before_save(:bye){$adds << 'byiyie'}
+ end
+
+ d.new.before_save
+ $adds.should == ['byiyie']
+
+ $adds = []
+ e = Class.new(Sequel::Model)
+ e.class_eval do
+ def bye; $adds << 'bye'; end
+ before_save :bye
+ before_save(:bye){$adds << 'byiyie'}
+ end
+
+ e.new.before_save
+ $adds.should == ['byiyie']
+
+ $adds = []
+ e = Class.new(Sequel::Model)
+ e.class_eval do
+ def bye; $adds << 'bye'; end
+ before_save(:bye){$adds << 'byiyie'}
+ before_save :bye
+ end
+
+ e.new.before_save
+ $adds.should == ['bye']
+ end
+
+ specify "should be inheritable" do
+ # pending
+
+ $adds = []
+ a = Class.new(Sequel::Model)
+ a.class_eval do
+ after_save {$adds << '123'}
+ end
+
+ b = Class.new(a)
+ b.class_eval do
+ after_save {$adds << '456'}
+ after_save {$adds << '789'}
+ end
+
+ b.new.after_save
+ $adds.should == ['123', '456', '789']
+ end
+
+ specify "should be overridable in descendant classes" do
+ $adds = []
+ a = Class.new(Sequel::Model)
+ a.class_eval do
+ before_save {$adds << '123'}
+ end
+
+ b = Class.new(a)
+ b.class_eval do
+ def before_save; $adds << '456'; end
+ end
+
+ a.new.before_save
+ $adds.should == ['123']
+ $adds = []
+ b.new.before_save
+ $adds.should == ['456']
+ end
+
+ specify "should stop processing if a hook returns false" do
+ $flag = true
+ $adds = []
+
+ a = Class.new(Sequel::Model)
+ a.class_eval do
+ after_save {$adds << 'blah'; $flag}
+ after_save {$adds << 'cruel'}
+ end
+
+ a.new.after_save
+ $adds.should == ['blah', 'cruel']
+
+ # chain should not break on nil
+ $adds = []
+ $flag = nil
+ a.new.after_save
+ $adds.should == ['blah', 'cruel']
+
+ $adds = []
+ $flag = false
+ a.new.after_save
+ $adds.should == ['blah']
+
+ b = Class.new(a)
+ b.class_eval do
+ after_save {$adds << 'mau'}
+ end
+
+ $adds = []
+ b.new.after_save
+ $adds.should == ['blah']
+ end
+end
+
+describe "Model#after_initialize" do
+ specify "should be called after initialization" do
+ $values1 = nil
+ $reached_after_initialized = false
+
+ a = Class.new(Sequel::Model)
+ a.class_eval do
+ columns :x, :y
+ after_initialize do
+ $values1 = @values.clone
+ $reached_after_initialized = true
+ end
+ end
+
+ a.new(:x => 1, :y => 2)
+ $values1.should == {:x => 1, :y => 2}
+ $reached_after_initialized.should == true
+ end
+end
+
+describe "Model#before_create && Model#after_create" do
+ setup do
+ MODEL_DB.reset
+
+ @c = Class.new(Sequel::Model(:items))
+ @c.class_eval do
+ columns :x
+ no_primary_key
+
+ after_create {MODEL_DB << "BLAH after"}
+ end
+ end
+
+ specify "should be called around new record creation" do
+ @c.before_create {MODEL_DB << "BLAH before"}
+ @c.create(:x => 2)
+ MODEL_DB.sqls.should == [
+ 'BLAH before',
+ 'INSERT INTO items (x) VALUES (2)',
+ 'BLAH after'
+ ]
+ end
+
+ specify ".create should cancel the save and raise an error if before_create returns false and raise_on_save_failure is true" do
+ @c.before_create{false}
+ proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed)
+ proc{@c.create(:x => 2)}.should raise_error(Sequel::BeforeHookFailed)
+ MODEL_DB.sqls.should == []
+ end
+
+ specify ".create should cancel the save and return nil if before_create returns false and raise_on_save_failure is false" do
+ @c.before_create{false}
+ @c.raise_on_save_failure = false
+ @c.create(:x => 2).should == nil
+ MODEL_DB.sqls.should == []
+ end
+end
+
+describe "Model#before_update && Model#after_update" do
+ setup do
+ MODEL_DB.reset
+
+ @c = Class.new(Sequel::Model(:items))
+ @c.class_eval do
+ after_update {MODEL_DB << "BLAH after"}
+ end
+ end
+
+ specify "should be called around record update" do
+ @c.before_update {MODEL_DB << "BLAH before"}
+ m = @c.load(:id => 2233)
+ m.save
+ MODEL_DB.sqls.should == [
+ 'BLAH before',
+ 'UPDATE items SET id = 2233 WHERE (id = 2233)',
+ 'BLAH after'
+ ]
+ end
+
+ specify "#save should cancel the save and raise an error if before_update returns false and raise_on_save_failure is true" do
+ @c.before_update{false}
+ proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed)
+ proc{@c.load(:id => 2233).save}.should raise_error(Sequel::BeforeHookFailed)
+ MODEL_DB.sqls.should == []
+ end
+
+ specify "#save should cancel the save and return nil if before_update returns false and raise_on_save_failure is false" do
+ @c.before_update{false}
+ @c.raise_on_save_failure = false
+ @c.load(:id => 2233).save.should == nil
+ MODEL_DB.sqls.should == []
+ end
+end
+
+describe "Model#before_save && Model#after_save" do
+ setup do
+ MODEL_DB.reset
+
+ @c = Class.new(Sequel::Model(:items))
+ @c.class_eval do
+ columns :x
+ after_save {MODEL_DB << "BLAH after"}
+ end
+ end
+
+ specify "should be called around record update" do
+ @c.before_save {MODEL_DB << "BLAH before"}
+ m = @c.load(:id => 2233)
+ m.save
+ MODEL_DB.sqls.should == [
+ 'BLAH before',
+ 'UPDATE items SET id = 2233 WHERE (id = 2233)',
+ 'BLAH after'
+ ]
+ end
+
+ specify "should be called around record creation" do
+ @c.before_save {MODEL_DB << "BLAH before"}
+ @c.no_primary_key
+ @c.create(:x => 2)
+ MODEL_DB.sqls.should == [
+ 'BLAH before',
+ 'INSERT INTO items (x) VALUES (2)',
+ 'BLAH after'
+ ]
+ end
+
+ specify "#save should cancel the save and raise an error if before_save returns false and raise_on_save_failure is true" do
+ @c.before_save{false}
+ proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed)
+ proc{@c.load(:id => 2233).save}.should raise_error(Sequel::BeforeHookFailed)
+ MODEL_DB.sqls.should == []
+ end
+
+ specify "#save should cancel the save and return nil if before_save returns false and raise_on_save_failure is false" do
+ @c.before_save{false}
+ @c.raise_on_save_failure = false
+ @c.load(:id => 2233).save.should == nil
+ MODEL_DB.sqls.should == []
+ end
+end
+
+describe "Model#before_destroy && Model#after_destroy" do
+ setup do
+ MODEL_DB.reset
+
+ @c = Class.new(Sequel::Model(:items))
+ @c.class_eval do
+ after_destroy {MODEL_DB << "BLAH after"}
+
+ def delete
+ MODEL_DB << "DELETE BLAH"
+ end
+ end
+ end
+
+ specify "should be called around record destruction" do
+ @c.before_destroy {MODEL_DB << "BLAH before"}
+ m = @c.load(:id => 2233)
+ m.destroy
+ MODEL_DB.sqls.should == [
+ 'BLAH before',
+ 'DELETE BLAH',
+ 'BLAH after'
+ ]
+ end
+
+ specify "#destroy should cancel the destroy and raise an error if before_destroy returns false and raise_on_save_failure is true" do
+ @c.before_destroy{false}
+ proc{@c.load(:id => 2233).destroy}.should raise_error(Sequel::BeforeHookFailed)
+ MODEL_DB.sqls.should == []
+ end
+
+ specify "#destroy should cancel the destroy and return nil if before_destroy returns false and raise_on_save_failure is false" do
+ @c.before_destroy{false}
+ @c.raise_on_save_failure = false
+ @c.load(:id => 2233).destroy.should == nil
+ MODEL_DB.sqls.should == []
+ end
+end
+
+describe "Model#before_validation && Model#after_validation" do
+ setup do
+ MODEL_DB.reset
+
+ @c = Class.new(Sequel::Model(:items))
+ @c.class_eval do
+ after_validation{MODEL_DB << "BLAH after"}
+
+ def self.validate(o)
+ o.errors[:id] << 'not valid' unless o[:id] == 2233
+ end
+ columns :id
+ end
+ end
+
+ specify "should be called around validation" do
+ @c.before_validation{MODEL_DB << "BLAH before"}
+ m = @c.load(:id => 2233)
+ m.should be_valid
+ MODEL_DB.sqls.should == ['BLAH before', 'BLAH after']
+
+ MODEL_DB.sqls.clear
+ m = @c.load(:id => 22)
+ m.should_not be_valid
+ MODEL_DB.sqls.should == ['BLAH before', 'BLAH after']
+ end
+
+ specify "should be called when calling save" do
+ @c.before_validation{MODEL_DB << "BLAH before"}
+ m = @c.load(:id => 2233)
+ m.save.should == m
+ MODEL_DB.sqls.should == ['BLAH before', 'BLAH after', 'UPDATE items SET id = 2233 WHERE (id = 2233)']
+
+ MODEL_DB.sqls.clear
+ m = @c.load(:id => 22)
+ m.raise_on_save_failure = false
+ m.save.should == nil
+ MODEL_DB.sqls.should == ['BLAH before', 'BLAH after']
+ end
+
+ specify "#save should cancel the save and raise an error if before_validation returns false and raise_on_save_failure is true" do
+ @c.before_validation{false}
+ proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed)
+ proc{@c.load(:id => 2233).save}.should raise_error(Sequel::BeforeHookFailed)
+ MODEL_DB.sqls.should == []
+ end
+
+ specify "#save should cancel the save and return nil if before_validation returns false and raise_on_save_failure is false" do
+ @c.before_validation{false}
+ @c.raise_on_save_failure = false
+ @c.load(:id => 2233).save.should == nil
+ MODEL_DB.sqls.should == []
+ end
+end
+
+describe "Model.has_hooks?" do
+ setup do
+ @c = Class.new(Sequel::Model(:items))
+ end
+
+ specify "should return false if no hooks are defined" do
+ @c.has_hooks?(:before_save).should be_false
+ end
+
+ specify "should return true if hooks are defined" do
+ @c.before_save {'blah'}
+ @c.has_hooks?(:before_save).should be_true
+ end
+
+ specify "should return true if hooks are inherited" do
+ @d = Class.new(@c)
+ @d.has_hooks?(:before_save).should be_false
+ end
+end
+
+describe "Model#add_hook_type" do
+ setup do
+ class Foo < Sequel::Model(:items)
+ add_hook_type :before_bar, :after_bar
+
+ def bar
+ return :b if before_bar == false
+ return :a if after_bar == false
+ true
+ end
+ end
+ @f = Class.new(Foo)
+ end
+
+ specify "should have before_bar and after_bar class methods" do
+ @f.should respond_to(:before_bar)
+ @f.should respond_to(:before_bar)
+ end
+
+ specify "should have before_bar and after_bar instance methods" do
+ @f.new.should respond_to(:before_bar)
+ @f.new.should respond_to(:before_bar)
+ end
+
+ specify "it should return true for bar when before_bar and after_bar hooks are returing true" do
+ a = 1
+ @f.before_bar { a += 1}
+ @f.new.bar.should be_true
+ a.should == 2
+ @f.after_bar { a *= 2}
+ @f.new.bar.should be_true
+ a.should == 6
+ end
+
+ specify "it should return nil for bar when before_bar and after_bar hooks are returing false" do
+ @f.new.bar.should be_true
+ @f.after_bar { false }
+ @f.new.bar.should == :a
+ @f.before_bar { false }
+ @f.new.bar.should == :b
+ end
+end
View
53 spec/extensions/single_table_inheritance_spec.rb
@@ -0,0 +1,53 @@
+require File.join(File.dirname(__FILE__), "spec_helper")
+
+describe Sequel::Model, "#sti_key" do
+ before do
+ class ::StiTest < Sequel::Model
+ def kind=(x); self[:kind] = x; end
+ def refresh; end
+ plugin :single_table_inheritance, :kind
+ end
+ class ::StiTestSub1 < StiTest
+ end
+ class ::StiTestSub2 < StiTest
+ end
+ @ds = StiTest.dataset
+ MODEL_DB.reset
+ end
+
+ it "should return rows with the correct class based on the polymorphic_key value" do
+ def @ds.fetch_rows(sql)
+ yield({:kind=>'StiTest'})
+ yield({:kind=>'StiTestSub1'})
+ yield({:kind=>'StiTestSub2'})
+ end
+ StiTest.all.collect{|x| x.class}.should == [StiTest, StiTestSub1, StiTestSub2]
+ end
+
+ it "should fallback to the main class if polymophic_key value is NULL" do
+ def @ds.fetch_rows(sql)
+ yield({:kind=>nil})
+ end
+ StiTest.all.collect{|x| x.class}.should == [StiTest]
+ end
+
+ it "should fallback to the main class if the given class does not exist" do
+ def @ds.fetch_rows(sql)
+ yield({:kind=>'StiTestSub3'})
+ end
+ StiTest.all.collect{|x| x.class}.should == [StiTest]
+ end
+
+ it "should add a before_create hook that sets the model class name for the key" do
+ StiTest.new.save
+ StiTestSub1.new.save
+ StiTestSub2.new.save
+ MODEL_DB.sqls.should == ["INSERT INTO sti_tests (kind) VALUES ('StiTest')", "INSERT INTO sti_tests (kind) VALUES ('StiTestSub1')", "INSERT INTO sti_tests (kind) VALUES ('StiTestSub2')"]
+ end
+
+ it "should add a filter to model datasets inside subclasses hook to only retreive objects with the matching key" do
+ StiTest.dataset.sql.should == "SELECT * FROM sti_tests"
+ StiTestSub1.dataset.sql.should == "SELECT * FROM sti_tests WHERE (kind = 'StiTestSub1')"
+ StiTestSub2.dataset.sql.should == "SELECT * FROM sti_tests WHERE (kind = 'StiTestSub2')"
+ end
+end
View
89 spec/extensions/spec_helper.rb
@@ -0,0 +1,89 @@
+require 'rubygems'
+unless Object.const_defined?('Sequel')
+ $:.unshift(File.join(File.dirname(__FILE__), "../../lib/"))
+ require 'sequel_core'
+end
+unless Sequel.const_defined?('Model')
+ $:.unshift(File.join(File.dirname(__FILE__), "../../lib/"))
+ require 'sequel_model'
+end
+
+Sequel.virtual_row_instance_eval = true
+
+extensions = %w'string_date_time'
+plugins = {:hook_class_methods=>[]}
+
+extensions.each{|e| require "sequel/extensions/#{e}"}
+plugins.each{|p, opts| Sequel::Model.plugin(p, *opts)}
+
+class MockDataset < Sequel::Dataset
+ def insert(*args)
+ @db.execute insert_sql(*args)
+ end
+
+ def update(*args)
+ @db.execute update_sql(*args)
+ end
+
+ def delete(*args)
+ @db.execute delete_sql(*args)
+ end
+
+ def fetch_rows(sql)
+ return if sql =~ /information_schema/
+ @db.execute(sql)
+ yield({:id => 1, :x => 1})
+ end
+
+ def quoted_identifier(c)
+ "\"#{c}\""
+ end
+end
+
+class MockDatabase < Sequel::Database
+ @@quote_identifiers = false
+ self.identifier_input_method = nil
+ self.identifier_output_method = nil
+ attr_reader :sqls
+
+ def execute(sql, opts={})
+ @sqls ||= []
+ @sqls << sql
+ end
+
+ def reset
+ @sqls = []
+ end
+
+ def schema(table_name, opts)
+ if table_name
+ [[:id, {:primary_key=>true}]]
+ else
+ {table_name=>[[:id, {:primary_key=>true}]]}
+ end
+ end
+
+ def transaction; yield; end
+
+ def dataset(opts=nil); MockDataset.new(self, opts); end
+end
+
+class << Sequel::Model
+ alias orig_columns columns
+ alias orig_str_columns str_columns
+ def columns(*cols)
+ return if cols.empty?
+ define_method(:columns){cols}
+ @dataset.instance_variable_set(:@columns, cols) if @dataset
+ define_method(:str_columns){cols.map{|x|x.to_s.freeze}}
+ def_column_accessor(*cols)
+ @columns = cols
+ @db_schema = {}
+ cols.each{|c| @db_schema[c] = {}}
+ end
+ def simple_table
+ nil
+ end
+end
+
+Sequel::Model.db = MODEL_DB = MockDatabase.new
View
93 spec/extensions/string_date_time_spec.rb
@@ -0,0 +1,93 @@
+require File.join(File.dirname(__FILE__), 'spec_helper')
+
+context "String#to_time" do
+ specify "should convert the string into a Time object" do
+ "2007-07-11".to_time.should == Time.parse("2007-07-11")
+ "06:30".to_time.should == Time.parse("06:30")
+ end
+
+ specify "should raise Error::InvalidValue for an invalid time" do
+ proc {'0000-00-00'.to_time}.should raise_error(Sequel::Error::InvalidValue)
+ end
+end
+
+context "String#to_date" do
+ after do
+ Sequel.convert_two_digit_years = true
+ end
+
+ specify "should convert the string into a Date object" do
+ "2007-07-11".to_date.should == Date.parse("2007-07-11")
+ end
+
+ specify "should convert 2 digit years by default" do
+ "July 11, 07".to_date.should == Date.parse("2007-07-11")
+ end
+
+ specify "should not convert 2 digit years if set not to" do
+ Sequel.convert_two_digit_years = false
+ "July 11, 07".to_date.should == Date.parse("0007-07-11")
+ end
+
+ specify "should raise Error::InvalidValue for an invalid date" do
+ proc {'0000-00-00'.to_date}.should raise_error(Sequel::Error::InvalidValue)
+ end
+end
+
+context "String#to_datetime" do
+ after do
+ Sequel.convert_two_digit_years = true
+ end
+
+ specify "should convert the string into a DateTime object" do
+ "2007-07-11 10:11:12a".to_datetime.should == DateTime.parse("2007-07-11 10:11:12a")
+ end
+
+ specify "should convert 2 digit years by default" do
+ "July 11, 07 10:11:12a".to_datetime.should == DateTime.parse("2007-07-11 10:11:12a")
+ end
+
+ specify "should not convert 2 digit years if set not to" do
+ Sequel.convert_two_digit_years = false
+ "July 11, 07 10:11:12a".to_datetime.should == DateTime.parse("0007-07-11 10:11:12a")
+ end
+
+ specify "should raise Error::InvalidValue for an invalid date" do
+ proc {'0000-00-00'.to_datetime}.should raise_error(Sequel::Error::InvalidValue)
+ end
+end
+
+context "String#to_sequel_time" do
+ after do
+ Sequel.datetime_class = Time
+ Sequel.convert_two_digit_years = true
+ end
+
+ specify "should convert the string into a Time object by default" do
+ "2007-07-11 10:11:12a".to_sequel_time.class.should == Time
+ "2007-07-11 10:11:12a".to_sequel_time.should == Time.parse("2007-07-11 10:11:12a")
+ end
+
+ specify "should convert the string into a DateTime object if that is set" do
+ Sequel.datetime_class = DateTime
+ "2007-07-11 10:11:12a".to_sequel_time.class.should == DateTime
+ "2007-07-11 10:11:12a".to_sequel_time.should == DateTime.parse("2007-07-11 10:11:12a")
+ end
+
+ specify "should convert 2 digit years by default if using DateTime class" do
+ Sequel.datetime_class = DateTime
+ "July 11, 07 10:11:12a".to_sequel_time.should == DateTime.parse("2007-07-11 10:11:12a")
+ end
+
+ specify "should not convert 2 digit years if set not to when using DateTime class" do
+ Sequel.datetime_class = DateTime
+ Sequel.convert_two_digit_years = false
+ "July 11, 07 10:11:12a".to_sequel_time.should == DateTime.parse("0007-07-11 10:11:12a")
+ end
+
+ specify "should raise Error::InvalidValue for an invalid time" do
+ proc {'0000-00-00'.to_sequel_time}.should raise_error(Sequel::Error::InvalidValue)
+ Sequel.datetime_class = DateTime
+ proc {'0000-00-00'.to_sequel_time}.should raise_error(Sequel::Error::InvalidValue)
+ end
+end
View
4 spec/sequel_model/base_spec.rb
@@ -475,8 +475,8 @@ def simple_table
Class.new(@c).simple_table.should == "'b'"
end
- it "should have simple_table = nil if inheriting and sti_key is set" do
- @c.set_sti_key :x
+ specify "should have simple_table = nil if inheriting and sti_key is set" do
+ @c.plugin :single_table_inheritance, :x
Class.new(@c).simple_table.should == nil
end
View
10 spec/sequel_model/caching_spec.rb
@@ -13,11 +13,13 @@ def get(k); self[k]; end
@cache = cache
@c = Class.new(Sequel::Model(:items))
- @c.class_eval do
- set_cache cache
- def self.name; 'Item' end
+ deprec do
+ @c.class_eval do
+ set_cache cache
+ def self.name; 'Item' end
- columns :name, :id
+ columns :name, :id
+ end
end
$cache_dataset_row = {:name => 'sharon', :id => 1}
View
106 spec/sequel_model/hooks_spec.rb
@@ -5,17 +5,7 @@
MODEL_DB.reset
end
- specify "should be definable using def <hook name>" do
- c = Class.new(Sequel::Model) do
- def before_save
- "hi there"
- end
- end
-
- c.new.before_save.should == 'hi there'
- end
-
- specify "should be definable using a block" do
+ deprec_specify "should be definable using a block" do
$adds = []
c = Class.new(Sequel::Model)
c.class_eval do
@@ -26,7 +16,7 @@ def before_save
$adds.should == ['hi']
end
- specify "should be definable using a method name" do
+ deprec_specify "should be definable using a method name" do
$adds = []
c = Class.new(Sequel::Model)
c.class_eval do
@@ -38,7 +28,7 @@ def bye; $adds << 'bye'; end
$adds.should == ['bye']
end
- specify "should be additive" do
+ deprec_specify "should be additive" do
$adds = []
c = Class.new(Sequel::Model)
c.class_eval do
@@ -50,7 +40,7 @@ def bye; $adds << 'bye'; end
$adds.should == ['hyiyie', 'byiyie']
end
- specify "before hooks should run in reverse order" do
+ deprec_specify "before hooks should run in reverse order" do
$adds = []
c = Class.new(Sequel::Model)
c.class_eval do
@@ -62,7 +52,7 @@ def bye; $adds << 'bye'; end
$adds.should == ['byiyie', 'hyiyie']
end
- specify "should not be additive if the method or tag already exists" do
+ deprec_specify "should not be additive if the method or tag already exists" do
$adds = []
c = Class.new(Sequel::Model)
c.class_eval do
@@ -107,9 +97,7 @@ def bye; $adds << 'bye'; end
$adds.should == ['bye']
end
- specify "should be inheritable" do
- # pending
-
+ deprec_specify "should be inheritable" do
$adds = []
a = Class.new(Sequel::Model)
a.class_eval do
@@ -126,7 +114,7 @@ def bye; $adds << 'bye'; end
$adds.should == ['123', '456', '789']
end
- specify "should be overridable in descendant classes" do
+ deprec_specify "should be overridable in descendant classes" do
$adds = []
a = Class.new(Sequel::Model)
a.class_eval do
@@ -145,7 +133,7 @@ def before_save; $adds << '456'; end
$adds.should == ['456']
end
- specify "should stop processing if a hook returns false" do
+ deprec_specify "should stop processing if a hook returns false" do
$flag = true
$adds = []
@@ -188,7 +176,7 @@ def before_save; $adds << '456'; end
a = Class.new(Sequel::Model)
a.class_eval do
columns :x, :y
- after_initialize do
+ def after_initialize
$values1 = @values.clone
$reached_after_initialized = true
end
@@ -209,12 +197,14 @@ def before_save; $adds << '456'; end
columns :x
no_primary_key
- after_create {MODEL_DB << "BLAH after"}
+ def after_create
+ MODEL_DB << "BLAH after"
+ end
end
end
specify "should be called around new record creation" do
- @c.before_create {MODEL_DB << "BLAH before"}
+ @c.send(:define_method, :before_create){MODEL_DB << "BLAH before"}
@c.create(:x => 2)
MODEL_DB.sqls.should == [
'BLAH before',
@@ -224,14 +214,14 @@ def before_save; $adds << '456'; end
end
specify ".create should cancel the save and raise an error if before_create returns false and raise_on_save_failure is true" do
- @c.before_create{false}
+ @c.send(:define_method, :before_create){false}
proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed)
proc{@c.create(:x => 2)}.should raise_error(Sequel::BeforeHookFailed)
MODEL_DB.sqls.should == []
end
specify ".create should cancel the save and return nil if before_create returns false and raise_on_save_failure is false" do
- @c.before_create{false}
+ @c.send(:define_method, :before_create){false}
@c.raise_on_save_failure = false
@c.create(:x => 2).should == nil
MODEL_DB.sqls.should == []
@@ -244,12 +234,12 @@ def before_save; $adds << '456'; end
@c = Class.new(Sequel::Model(:items))
@c.class_eval do
- after_update {MODEL_DB << "BLAH after"}
+ def after_update; MODEL_DB << "BLAH after" end
end
end
specify "should be called around record update" do
- @c.before_update {MODEL_DB << "BLAH before"}
+ @c.send(:define_method, :before_update){MODEL_DB << "BLAH before"}
m = @c.load(:id => 2233)
m.save
MODEL_DB.sqls.should == [
@@ -260,14 +250,14 @@ def before_save; $adds << '456'; end
end
specify "#save should cancel the save and raise an error if before_update returns false and raise_on_save_failure is true" do
- @c.before_update{false}
+ @c.send(:define_method, :before_update){false}
proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed)
proc{@c.load(:id => 2233).save}.should raise_error(Sequel::BeforeHookFailed)
MODEL_DB.sqls.should == []
end
specify "#save should cancel the save and return nil if before_update returns false and raise_on_save_failure is false" do
- @c.before_update{false}
+ @c.send(:define_method, :before_update){false}
@c.raise_on_save_failure = false
@c.load(:id => 2233).save.should == nil
MODEL_DB.sqls.should == []
@@ -281,12 +271,12 @@ def before_save; $adds << '456'; end
@c = Class.new(Sequel::Model(:items))
@c.class_eval do
columns :x
- after_save {MODEL_DB << "BLAH after"}
+ def after_save; MODEL_DB << "BLAH after" end
end
end
specify "should be called around record update" do
- @c.before_save {MODEL_DB << "BLAH before"}
+ @c.send(:define_method, :before_save){MODEL_DB << "BLAH before"}
m = @c.load(:id => 2233)
m.save
MODEL_DB.sqls.should == [
@@ -297,7 +287,7 @@ def before_save; $adds << '456'; end
end
specify "should be called around record creation" do
- @c.before_save {MODEL_DB << "BLAH before"}
+ @c.send(:define_method, :before_save){MODEL_DB << "BLAH before"}
@c.no_primary_key
@c.create(:x => 2)
MODEL_DB.sqls.should == [
@@ -308,14 +298,14 @@ def before_save; $adds << '456'; end
end
specify "#save should cancel the save and raise an error if before_save returns false and raise_on_save_failure is true" do
- @c.before_save{false}
+ @c.send(:define_method, :before_save){false}
proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed)
proc{@c.load(:id => 2233).save}.should raise_error(Sequel::BeforeHookFailed)
MODEL_DB.sqls.should == []
end
specify "#save should cancel the save and return nil if before_save returns false and raise_on_save_failure is false" do
- @c.before_save{false}
+ @c.send(:define_method, :before_save){false}
@c.raise_on_save_failure = false
@c.load(:id => 2233).save.should == nil
MODEL_DB.sqls.should == []
@@ -328,7 +318,7 @@ def before_save; $adds << '456'; end
@c = Class.new(Sequel::Model(:items))
@c.class_eval do
- after_destroy {MODEL_DB << "BLAH after"}
+ def after_destroy; MODEL_DB << "BLAH after"; end
def delete
MODEL_DB << "DELETE BLAH"
@@ -337,7 +327,7 @@ def delete
end
specify "should be called around record destruction" do
- @c.before_destroy {MODEL_DB << "BLAH before"}
+ @c.send(:define_method, :before_destroy){MODEL_DB << "BLAH before"}
m = @c.load(:id => 2233)
m.destroy
MODEL_DB.sqls.should == [
@@ -348,13 +338,13 @@ def delete
end
specify "#destroy should cancel the destroy and raise an error if before_destroy returns false and raise_on_save_failure is true" do
- @c.before_destroy{false}
+ @c.send(:define_method, :before_destroy){false}
proc{@c.load(:id => 2233).destroy}.should raise_error(Sequel::BeforeHookFailed)
MODEL_DB.sqls.should == []
end
specify "#destroy should cancel the destroy and return nil if before_destroy returns false and raise_on_save_failure is false" do
- @c.before_destroy{false}
+ @c.send(:define_method, :before_destroy){false}
@c.raise_on_save_failure = false
@c.load(:id => 2233).destroy.should == nil
MODEL_DB.sqls.should == []
@@ -367,7 +357,7 @@ def delete
@c = Class.new(Sequel::Model(:items))
@c.class_eval do
- after_validation{MODEL_DB << "BLAH after"}
+ def after_validation; MODEL_DB << "BLAH after" end
def self.validate(o)
o.errors[:id] << 'not valid' unless o[:id] == 2233
@@ -377,7 +367,7 @@ def self.validate(o)
end
specify "should be called around validation" do
- @c.before_validation{MODEL_DB << "BLAH before"}
+ @c.send(:define_method, :before_validation){MODEL_DB << "BLAH before"}
m = @c.load(:id => 2233)
m.should be_valid
MODEL_DB.sqls.should == ['BLAH before', 'BLAH after']
@@ -389,7 +379,7 @@ def self.validate(o)
end
specify "should be called when calling save" do
- @c.before_validation{MODEL_DB << "BLAH before"}
+ @c.send(:define_method, :before_validation){MODEL_DB << "BLAH before"}
m = @c.load(:id => 2233)
m.save.should == m
MODEL_DB.sqls.should == ['BLAH before', 'BLAH after', 'UPDATE items SET id = 2233 WHERE (id = 2233)']
@@ -402,14 +392,14 @@ def self.validate(o)
end
specify "#save should cancel the save and raise an error if before_validation returns false and raise_on_save_failure is true" do
- @c.before_validation{false}
+ @c.send(:define_method, :before_validation){false}
proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed)
proc{@c.load(:id => 2233).save}.should raise_error(Sequel::BeforeHookFailed)
MODEL_DB.sqls.should == []
end
specify "#save should cancel the save and return nil if before_validation returns false and raise_on_save_failure is false" do
- @c.before_validation{false}
+ @c.send(:define_method, :before_validation){false}
@c.raise_on_save_failure = false
@c.load(:id => 2233).save.should == nil
MODEL_DB.sqls.should == []
@@ -421,16 +411,16 @@ def self.validate(o)
@c = Class.new(Sequel::Model(:items))
end
- specify "should return false if no hooks are defined" do
+ deprec_specify "should return false if no hooks are defined" do
@c.has_hooks?(:before_save).should be_false
end
- specify "should return true if hooks are defined" do
+ deprec_specify "should return true if hooks are defined" do
@c.before_save {'blah'}
@c.has_hooks?(:before_save).should be_true
end
- specify "should return true if hooks are inherited" do
+ deprec_specify "should return true if hooks are inherited" do
@d = Class.new(@c)
@d.has_hooks?(:before_save).should be_false
end
@@ -438,29 +428,31 @@ def self.validate(o)
describe "Model#add_hook_type" do
setup do
- class Foo < Sequel::Model(:items)
- add_hook_type :before_bar, :after_bar
-
- def bar
- return :b if before_bar == false
- return :a if after_bar == false
- true
+ deprec do
+ class Foo < Sequel::Model(:items)
+ add_hook_type :before_bar, :after_bar
+
+ def bar
+ return :b if before_bar == false
+ return :a if after_bar == false
+ true
+ end
end
end
@f = Class.new(Foo)
end
- specify "should have before_bar and after_bar class methods" do
+ deprec_specify "should have before_bar and after_bar class methods" do
@f.should respond_to(:before_bar)
@f.should respond_to(:before_bar)
end
- specify "should have before_bar and after_bar instance methods" do
+ deprec_specify "should have before_bar and after_bar instance methods" do
@f.new.should respond_to(:before_bar)
@f.new.should respond_to(:before_bar)
end
- specify "it should return true for bar when before_bar and after_bar hooks are returing true" do
+ deprec_specify "it should return true for bar when before_bar and after_bar hooks are returing true" do
a = 1
@f.before_bar { a += 1}
@f.new.bar.should be_true
@@ -470,7 +462,7 @@ def bar
a.should == 6
end
- specify "it should return nil for bar when before_bar and after_bar hooks are returing false" do
+ deprec_specify "it should return nil for bar when before_bar and after_bar hooks are returing false" do
@f.new.bar.should be_true
@f.after_bar { false }
@f.new.bar.should == :a
View
20 spec/sequel_model/model_spec.rb
@@ -93,10 +93,12 @@
describe Sequel::Model, "#sti_key" do
before do
- class ::StiTest < Sequel::Model
- def kind=(x); self[:kind] = x; end
- def refresh; end
- set_sti_key :kind
+ deprec do
+ class ::StiTest < Sequel::Model
+ def kind=(x); self[:kind] = x; end
+ def refresh; end
+ set_sti_key :kind
+ end
end
class ::StiTestSub1 < StiTest
end
@@ -106,7 +108,7 @@ class ::StiTestSub2 < StiTest
MODEL_DB.reset
end
- it "should return rows with the correct class based on the polymorphic_key value" do
+ deprec_specify "should return rows with the correct class based on the polymorphic_key value" do
def @ds.fetch_rows(sql)
yield({:kind=>'StiTest'})
yield({:kind=>'StiTestSub1'})
@@ -115,28 +117,28 @@ def @ds.fetch_rows(sql)
StiTest.all.collect{|x| x.class}.should == [StiTest, StiTestSub1, StiTestSub2]
end
- it "should fallback to the main class if polymophic_key value is NULL" do
+ deprec_specify "should fallback to the main class if polymophic_key value is NULL" do
def @ds.fetch_rows(sql)
yield({:kind=>nil})
end
StiTest.all.collect{|x| x.class}.should == [StiTest]
end
- it "should fallback to the main class if the given class does not exist" do
+ deprec_specify "should fallback to the main class if the given class does not exist" do
def @ds.fetch_rows(sql)
yield({:kind=>'StiTestSub3'})
end
StiTest.all.collect{|x| x.class}.should == [StiTest]
end
- it "should add a before_create hook that sets the model class name for the key" do
+ deprec_specify "should add a before_create hook that sets the model class name for the key" do
StiTest.new.save
StiTestSub1.new.save
StiTestSub2.new.save
MODEL_DB.sqls.should == ["INSERT INTO sti_tests (kind) VALUES ('StiTest')", "INSERT INTO sti_tests (kind) VALUES ('StiTestSub1')", "INSERT INTO sti_tests (kind) VALUES ('StiTestSub2')"]
end
- it "should add a filter to model datasets inside subclasses hook to only retreive objects with the matching key" do
+ deprec_specify "should add a filter to model datasets inside subclasses hook to only retreive objects with the matching key" do
StiTest.dataset.sql.should == "SELECT * FROM sti_tests"
StiTestSub1.dataset.sql.should == "SELECT * FROM sti_tests WHERE (kind = 'StiTestSub1')"
StiTestSub2.dataset.sql.should == "SELECT * FROM sti_tests WHERE (kind = 'StiTestSub2')"
View
2  spec/sequel_model/plugins_spec.rb
@@ -5,7 +5,7 @@ module Sequel::Plugins
module Timestamped
def self.apply(m, opts)
m.meta_def(:stamp_opts) {opts}
- m.before_save {@values[:stamp] = Time.now}
+ m.send(:define_method, :before_save){@values[:stamp] = Time.now}
end
module InstanceMethods
View
12 spec/sequel_model/record_spec.rb
@@ -57,7 +57,7 @@ def ds.insert_select(hash)
it "should preserve changed_columns' and @new's value until all hook finish running" do
res = nil
- @c.after_save { res = [changed_columns, @new].flatten}
+ @c.send(:define_method, :after_save){ res = [changed_columns, @new].flatten}
o = @c.new(:x => 1, :y => nil)
o[:x] = 2
o.save
@@ -142,7 +142,7 @@ def ds.insert_select(hash)
it "should update columns changed in a before_update hook" do
o = @c.load(:id => 3, :x => 1, :y => nil)
- @c.before_update{self.x += 1}
+ @c.send(:define_method, :before_update){self.x += 1}
o.save_changes
MODEL_DB.sqls.should == []
o.x = 2
@@ -159,7 +159,7 @@ def ds.insert_select(hash)
it "should update columns changed in a before_save hook" do
o = @c.load(:id => 3, :x => 1, :y => nil)
- @c.before_save{self.x += 1}
+ @c.send(:define_method, :before_update){self.x += 1}
o.save_changes
MODEL_DB.sqls.should == []
o.x = 2
@@ -590,7 +590,7 @@ def ds.insert_select(hash)
it "should return self" do
@model.db.should_receive(:transaction)
- @model.after_destroy{3}
+ @model.send(:define_method, :after_destroy){3}
@instance.destroy.should == @instance
end