sequel 2.11.0 → 2.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +168 -0
- data/README.rdoc +77 -95
- data/Rakefile +100 -80
- data/bin/sequel +2 -1
- data/doc/advanced_associations.rdoc +23 -32
- data/doc/cheat_sheet.rdoc +23 -40
- data/doc/dataset_filtering.rdoc +6 -6
- data/doc/prepared_statements.rdoc +22 -22
- data/doc/release_notes/2.12.0.txt +534 -0
- data/doc/schema.rdoc +3 -1
- data/doc/sharding.rdoc +8 -8
- data/doc/virtual_rows.rdoc +65 -0
- data/lib/sequel.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/ado.rb +3 -3
- data/lib/{sequel_core → sequel}/adapters/db2.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/dbi.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/do.rb +9 -5
- data/lib/{sequel_core → sequel}/adapters/do/mysql.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/do/postgres.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/do/sqlite.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/firebird.rb +84 -80
- data/lib/{sequel_core → sequel}/adapters/informix.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc.rb +21 -14
- data/lib/{sequel_core → sequel}/adapters/jdbc/h2.rb +14 -13
- data/lib/{sequel_core → sequel}/adapters/jdbc/mysql.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc/oracle.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc/postgresql.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc/sqlite.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/mysql.rb +60 -39
- data/lib/{sequel_core → sequel}/adapters/odbc.rb +8 -4
- data/lib/{sequel_core → sequel}/adapters/openbase.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/oracle.rb +38 -7
- data/lib/{sequel_core → sequel}/adapters/postgres.rb +24 -24
- data/lib/{sequel_core → sequel}/adapters/shared/mssql.rb +5 -5
- data/lib/{sequel_core → sequel}/adapters/shared/mysql.rb +126 -71
- data/lib/{sequel_core → sequel}/adapters/shared/oracle.rb +7 -10
- data/lib/{sequel_core → sequel}/adapters/shared/postgres.rb +159 -125
- data/lib/{sequel_core → sequel}/adapters/shared/progress.rb +1 -2
- data/lib/{sequel_core → sequel}/adapters/shared/sqlite.rb +72 -67
- data/lib/{sequel_core → sequel}/adapters/sqlite.rb +11 -7
- data/lib/{sequel_core → sequel}/adapters/utils/date_format.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/utils/stored_procedures.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/utils/unsupported.rb +19 -0
- data/lib/{sequel_core → sequel}/connection_pool.rb +7 -5
- data/lib/sequel/core.rb +221 -0
- data/lib/{sequel_core → sequel}/core_sql.rb +91 -49
- data/lib/{sequel_core → sequel}/database.rb +264 -149
- data/lib/{sequel_core/schema/generator.rb → sequel/database/schema_generator.rb} +6 -2
- data/lib/{sequel_core/database/schema.rb → sequel/database/schema_methods.rb} +12 -12
- data/lib/sequel/database/schema_sql.rb +224 -0
- data/lib/{sequel_core → sequel}/dataset.rb +78 -236
- data/lib/{sequel_core → sequel}/dataset/convenience.rb +99 -61
- data/lib/{sequel_core/object_graph.rb → sequel/dataset/graph.rb} +16 -14
- data/lib/{sequel_core → sequel}/dataset/prepared_statements.rb +1 -1
- data/lib/{sequel_core → sequel}/dataset/sql.rb +150 -99
- data/lib/sequel/deprecated.rb +593 -0
- data/lib/sequel/deprecated_migration.rb +91 -0
- data/lib/sequel/exceptions.rb +48 -0
- data/lib/sequel/extensions/blank.rb +42 -0
- data/lib/{sequel_model → sequel/extensions}/inflector.rb +8 -1
- data/lib/{sequel_core → sequel/extensions}/migration.rb +1 -1
- data/lib/{sequel_core/dataset → sequel/extensions}/pagination.rb +0 -0
- data/lib/{sequel_core → sequel/extensions}/pretty_table.rb +7 -0
- data/lib/{sequel_core/dataset → sequel/extensions}/query.rb +7 -0
- data/lib/sequel/extensions/string_date_time.rb +47 -0
- data/lib/sequel/metaprogramming.rb +43 -0
- data/lib/sequel/model.rb +110 -0
- data/lib/sequel/model/associations.rb +1300 -0
- data/lib/sequel/model/base.rb +937 -0
- data/lib/sequel/model/deprecated.rb +204 -0
- data/lib/sequel/model/deprecated_hooks.rb +103 -0
- data/lib/sequel/model/deprecated_inflector.rb +335 -0
- data/lib/sequel/model/deprecated_validations.rb +388 -0
- data/lib/sequel/model/errors.rb +39 -0
- data/lib/{sequel_model → sequel/model}/exceptions.rb +4 -4
- data/lib/sequel/model/inflections.rb +208 -0
- data/lib/sequel/model/plugins.rb +76 -0
- data/lib/sequel/plugins/caching.rb +122 -0
- data/lib/sequel/plugins/hook_class_methods.rb +122 -0
- data/lib/sequel/plugins/schema.rb +53 -0
- data/lib/sequel/plugins/serialization.rb +117 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
- data/lib/sequel/plugins/validation_class_methods.rb +384 -0
- data/lib/sequel/plugins/validation_helpers.rb +150 -0
- data/lib/{sequel_core → sequel}/sql.rb +125 -190
- data/lib/{sequel_core → sequel}/version.rb +2 -1
- data/lib/sequel_core.rb +1 -172
- data/lib/sequel_model.rb +1 -91
- data/spec/adapters/firebird_spec.rb +5 -5
- data/spec/adapters/informix_spec.rb +1 -1
- data/spec/adapters/mysql_spec.rb +128 -42
- data/spec/adapters/oracle_spec.rb +47 -19
- data/spec/adapters/postgres_spec.rb +64 -52
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +12 -17
- data/spec/{sequel_core → core}/connection_pool_spec.rb +10 -10
- data/spec/{sequel_core → core}/core_ext_spec.rb +19 -19
- data/spec/{sequel_core → core}/core_sql_spec.rb +68 -71
- data/spec/{sequel_core → core}/database_spec.rb +135 -99
- data/spec/{sequel_core → core}/dataset_spec.rb +398 -242
- data/spec/{sequel_core → core}/expression_filters_spec.rb +13 -13
- data/spec/core/migration_spec.rb +263 -0
- data/spec/{sequel_core → core}/object_graph_spec.rb +10 -10
- data/spec/{sequel_core → core}/pretty_table_spec.rb +2 -2
- data/spec/{sequel_core → core}/schema_generator_spec.rb +0 -0
- data/spec/{sequel_core → core}/schema_spec.rb +8 -10
- data/spec/{sequel_core → core}/spec_helper.rb +29 -2
- data/spec/{sequel_core → core}/version_spec.rb +0 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/caching_spec.rb +201 -0
- data/spec/{sequel_model/hooks_spec.rb → extensions/hook_class_methods_spec.rb} +8 -23
- data/spec/{sequel_model → extensions}/inflector_spec.rb +3 -0
- data/spec/{sequel_core → extensions}/migration_spec.rb +4 -4
- data/spec/extensions/pagination_spec.rb +99 -0
- data/spec/extensions/pretty_table_spec.rb +91 -0
- data/spec/extensions/query_spec.rb +85 -0
- data/spec/{sequel_model → extensions}/schema_spec.rb +22 -1
- data/spec/extensions/serialization_spec.rb +109 -0
- data/spec/extensions/single_table_inheritance_spec.rb +53 -0
- data/spec/{sequel_model → extensions}/spec_helper.rb +13 -4
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/{sequel_model/validations_spec.rb → extensions/validation_class_methods_spec.rb} +15 -103
- data/spec/extensions/validation_helpers_spec.rb +291 -0
- data/spec/integration/dataset_test.rb +31 -0
- data/spec/integration/eager_loader_test.rb +17 -30
- data/spec/integration/schema_test.rb +8 -5
- data/spec/integration/spec_helper.rb +17 -0
- data/spec/integration/transaction_test.rb +68 -0
- data/spec/{sequel_model → model}/association_reflection_spec.rb +0 -0
- data/spec/{sequel_model → model}/associations_spec.rb +23 -10
- data/spec/{sequel_model → model}/base_spec.rb +29 -20
- data/spec/{sequel_model → model}/caching_spec.rb +16 -14
- data/spec/{sequel_model → model}/dataset_methods_spec.rb +0 -0
- data/spec/{sequel_model → model}/eager_loading_spec.rb +8 -8
- data/spec/model/hooks_spec.rb +472 -0
- data/spec/model/inflector_spec.rb +126 -0
- data/spec/{sequel_model → model}/model_spec.rb +25 -20
- data/spec/model/plugins_spec.rb +142 -0
- data/spec/{sequel_model → model}/record_spec.rb +121 -62
- data/spec/model/schema_spec.rb +92 -0
- data/spec/model/spec_helper.rb +124 -0
- data/spec/model/validations_spec.rb +1080 -0
- metadata +136 -107
- data/lib/sequel_core/core_ext.rb +0 -217
- data/lib/sequel_core/dataset/callback.rb +0 -13
- data/lib/sequel_core/dataset/schema.rb +0 -15
- data/lib/sequel_core/deprecated.rb +0 -26
- data/lib/sequel_core/exceptions.rb +0 -44
- data/lib/sequel_core/schema.rb +0 -2
- data/lib/sequel_core/schema/sql.rb +0 -325
- data/lib/sequel_model/association_reflection.rb +0 -267
- data/lib/sequel_model/associations.rb +0 -499
- data/lib/sequel_model/base.rb +0 -539
- data/lib/sequel_model/caching.rb +0 -82
- data/lib/sequel_model/dataset_methods.rb +0 -26
- data/lib/sequel_model/eager_loading.rb +0 -370
- data/lib/sequel_model/hooks.rb +0 -101
- data/lib/sequel_model/plugins.rb +0 -62
- data/lib/sequel_model/record.rb +0 -568
- data/lib/sequel_model/schema.rb +0 -49
- data/lib/sequel_model/validations.rb +0 -429
- data/spec/sequel_model/plugins_spec.rb +0 -80
@@ -0,0 +1,76 @@
|
|
1
|
+
module Sequel
|
2
|
+
# Empty namespace that plugins should use to store themselves,
|
3
|
+
# so they can be loaded via Model.plugin.
|
4
|
+
#
|
5
|
+
# Plugins should be modules with one of the following conditions:
|
6
|
+
# * A singleton method named apply, which takes a model and
|
7
|
+
# additional arguments.
|
8
|
+
# * A module inside the plugin module named InstanceMethods,
|
9
|
+
# which will be included in the model class.
|
10
|
+
# * A module inside the plugin module named ClassMethods,
|
11
|
+
# which will extend the model class.
|
12
|
+
# * A module inside the plugin module named DatasetMethods,
|
13
|
+
# which will extend the model's dataset.
|
14
|
+
module Plugins
|
15
|
+
end
|
16
|
+
|
17
|
+
class Model
|
18
|
+
# Loads a plugin for use with the model class, passing optional arguments
|
19
|
+
# to the plugin. If the plugin is a module, load it directly. Otherwise,
|
20
|
+
# require the plugin from either sequel/plugins/#{plugin} or
|
21
|
+
# sequel_#{plugin}, and then attempt to load the module using a
|
22
|
+
# the camelized plugin name under Sequel::Plugins.
|
23
|
+
def self.plugin(plugin, *args)
|
24
|
+
arg = args.first
|
25
|
+
block = lambda{arg}
|
26
|
+
m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
|
27
|
+
if m.respond_to?(:apply)
|
28
|
+
m.apply(self, *args)
|
29
|
+
end
|
30
|
+
if m.const_defined?("InstanceMethods")
|
31
|
+
define_method(:"#{plugin}_opts", &block)
|
32
|
+
include(m::InstanceMethods)
|
33
|
+
end
|
34
|
+
if m.const_defined?("ClassMethods")
|
35
|
+
meta_def(:"#{plugin}_opts", &block)
|
36
|
+
extend(m::ClassMethods)
|
37
|
+
end
|
38
|
+
if m.const_defined?("DatasetMethods")
|
39
|
+
if @dataset
|
40
|
+
dataset.meta_def(:"#{plugin}_opts", &block)
|
41
|
+
dataset.extend(m::DatasetMethods)
|
42
|
+
end
|
43
|
+
dataset_method_modules << m::DatasetMethods
|
44
|
+
def_dataset_method(*m::DatasetMethods.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s})
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
private
|
50
|
+
|
51
|
+
# Returns the new style location for the plugin name.
|
52
|
+
def plugin_gem_location(plugin)
|
53
|
+
"sequel/plugins/#{plugin}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the old style location for the plugin name.
|
57
|
+
def plugin_gem_location_old(plugin)
|
58
|
+
"sequel_#{plugin}"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the module for the specified plugin. If the module is not
|
62
|
+
# defined, the corresponding plugin gem is automatically loaded.
|
63
|
+
def plugin_module(plugin)
|
64
|
+
module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
|
65
|
+
if not Sequel::Plugins.const_defined?(module_name)
|
66
|
+
begin
|
67
|
+
require plugin_gem_location(plugin)
|
68
|
+
rescue LoadError
|
69
|
+
require plugin_gem_location_old(plugin)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
Sequel::Plugins.const_get(module_name)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# Sequel's built-in caching plugin supports caching to any object that
|
4
|
+
# implements the Ruby-Memcache API. You can add caching for any model
|
5
|
+
# or for all models via:
|
6
|
+
#
|
7
|
+
# Model.plugin :caching, store # Cache all models
|
8
|
+
# MyModel.plugin :caching, store # Just cache MyModel
|
9
|
+
#
|
10
|
+
# The cache store should implement the Ruby-Memcache API:
|
11
|
+
#
|
12
|
+
# cache_store.set(key, obj, time) # Associate the obj with the given key
|
13
|
+
# # in the cache for the time (specified
|
14
|
+
# # in seconds)
|
15
|
+
# cache_store.get(key) => obj # Returns object set with same key
|
16
|
+
# cache_store.get(key2) => nil # nil returned if there isn't an object
|
17
|
+
# # currently in the cache with that key
|
18
|
+
module Caching
|
19
|
+
# Set the cache_store and cache_ttl attributes for the given model.
|
20
|
+
# If the :ttl option is not given, 3600 seconds is the default.
|
21
|
+
def self.apply(model, store, opts={})
|
22
|
+
model.instance_eval do
|
23
|
+
@cache_store = store
|
24
|
+
@cache_ttl = opts[:ttl] || 3600
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
# The cache store object for the model, which should implement the
|
30
|
+
# Ruby-Memcache API
|
31
|
+
attr_reader :cache_store
|
32
|
+
|
33
|
+
# The time to live for the cache store, in seconds.
|
34
|
+
attr_reader :cache_ttl
|
35
|
+
|
36
|
+
# Check the cache before a database lookup unless a hash is supplied.
|
37
|
+
def [](*args)
|
38
|
+
args = args.first if (args.size == 1)
|
39
|
+
return super(args) if args.is_a?(Hash)
|
40
|
+
ck = cache_key(args)
|
41
|
+
if obj = @cache_store.get(ck)
|
42
|
+
return obj
|
43
|
+
end
|
44
|
+
if obj = super(args)
|
45
|
+
@cache_store.set(ck, obj, @cache_ttl)
|
46
|
+
end
|
47
|
+
obj
|
48
|
+
end
|
49
|
+
|
50
|
+
# Set the time to live for the cache store, in seconds (default is 3600, # so 1 hour).
|
51
|
+
def set_cache_ttl(ttl)
|
52
|
+
@cache_ttl = ttl
|
53
|
+
end
|
54
|
+
|
55
|
+
# Copy the cache_store and cache_ttl to the subclass.
|
56
|
+
def inherited(subclass)
|
57
|
+
super
|
58
|
+
store = @cache_store
|
59
|
+
ttl = @cache_ttl
|
60
|
+
subclass.instance_eval do
|
61
|
+
@cache_store = store
|
62
|
+
@cache_ttl = ttl
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# Delete the entry with the matching key from the cache
|
69
|
+
def cache_delete(key)
|
70
|
+
@cache_store.delete(key)
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return a key string for the pk
|
75
|
+
def cache_key(pk)
|
76
|
+
"#{self}:#{Array(pk).join(',')}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module InstanceMethods
|
81
|
+
# Remove the object from the cache when updating
|
82
|
+
def before_update
|
83
|
+
return false if super == false
|
84
|
+
cache_delete
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return a key unique to the underlying record for caching, based on the
|
88
|
+
# primary key value(s) for the object. If the model does not have a primary
|
89
|
+
# key, raise an Error.
|
90
|
+
def cache_key
|
91
|
+
raise(Error, "No primary key is associated with this model") unless key = primary_key
|
92
|
+
pk = case key
|
93
|
+
when Array
|
94
|
+
key.collect{|k| @values[k]}
|
95
|
+
else
|
96
|
+
@values[key] || (raise Error, 'no primary key for this record')
|
97
|
+
end
|
98
|
+
model.send(:cache_key, pk)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Remove the object from the cache when deleting
|
102
|
+
def delete
|
103
|
+
cache_delete
|
104
|
+
super
|
105
|
+
end
|
106
|
+
|
107
|
+
# Remove the object from the cache when updating
|
108
|
+
def update_values(*args)
|
109
|
+
cache_delete
|
110
|
+
super
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Delete this object from the cache
|
116
|
+
def cache_delete
|
117
|
+
model.send(:cache_delete, cache_key)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# Sequel's built-in hook class methods plugin is designed for backwards
|
4
|
+
# compatibility. Its use is not encouraged, it is recommended to use
|
5
|
+
# instance methods and super instead of this plugin. What this plugin
|
6
|
+
# allows you to do is, for example:
|
7
|
+
#
|
8
|
+
# # Block only, can cause duplicate hooks if code is reloaded
|
9
|
+
# before_save{self.created_at = Time.now}
|
10
|
+
# # Block with tag, safe for reloading
|
11
|
+
# before_save(:set_created_at){self.created_at = Time.now}
|
12
|
+
# # Tag only, safe for reloading, calls instance method
|
13
|
+
# before_save(:set_created_at)
|
14
|
+
#
|
15
|
+
# Pretty much anything you can do with a hook class method, you can also
|
16
|
+
# do with an instance method instead:
|
17
|
+
#
|
18
|
+
# def before_save
|
19
|
+
# return false if super == false
|
20
|
+
# self.created_at = Time.now
|
21
|
+
# end
|
22
|
+
module HookClassMethods
|
23
|
+
# Set up the hooks instance variable in the model.
|
24
|
+
def self.apply(model)
|
25
|
+
hooks = model.instance_variable_set(:@hooks, {})
|
26
|
+
Model::HOOKS.each{|h| hooks[h] = []}
|
27
|
+
end
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
Model::HOOKS.each{|h| class_eval("def #{h}(method = nil, &block); add_hook(:#{h}, method, &block) end", __FILE__, __LINE__)}
|
31
|
+
|
32
|
+
# This adds a new hook type. It will define both a class
|
33
|
+
# method that you can use to add hooks, as well as an instance method
|
34
|
+
# that you can use to call all hooks of that type. The class method
|
35
|
+
# can be called with a symbol or a block or both. If a block is given and
|
36
|
+
# and symbol is not, it adds the hook block to the hook type. If a block
|
37
|
+
# and symbol are both given, it replaces the hook block associated with
|
38
|
+
# that symbol for a given hook type, or adds it if there is no hook block
|
39
|
+
# with that symbol for that hook type. If no block is given, it assumes
|
40
|
+
# the symbol specifies an instance method to call and adds it to the hook
|
41
|
+
# type.
|
42
|
+
#
|
43
|
+
# If any hook block returns false, the instance method will return false
|
44
|
+
# immediately without running the rest of the hooks of that type.
|
45
|
+
#
|
46
|
+
# It is recommended that you always provide a symbol to this method,
|
47
|
+
# for descriptive purposes. It's only necessary to do so when you
|
48
|
+
# are using a system that reloads code.
|
49
|
+
#
|
50
|
+
# Example of usage:
|
51
|
+
#
|
52
|
+
# class MyModel
|
53
|
+
# define_hook :before_move_to
|
54
|
+
# before_move_to(:check_move_allowed){|o| o.allow_move?}
|
55
|
+
# def move_to(there)
|
56
|
+
# return if before_move_to == false
|
57
|
+
# # move MyModel object to there
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
def add_hook_type(*hooks)
|
61
|
+
Model::HOOKS.concat(hooks)
|
62
|
+
hooks.each do |hook|
|
63
|
+
@hooks[hook] = []
|
64
|
+
instance_eval("def #{hook}(method = nil, &block); add_hook(:#{hook}, method, &block) end", __FILE__, __LINE__)
|
65
|
+
class_eval("def #{hook}; run_hooks(:#{hook}); end", __FILE__, __LINE__)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns true if there are any hook blocks for the given hook.
|
70
|
+
def has_hooks?(hook)
|
71
|
+
!@hooks[hook].empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Yield every block related to the given hook.
|
75
|
+
def hook_blocks(hook)
|
76
|
+
@hooks[hook].each{|k,v| yield v}
|
77
|
+
end
|
78
|
+
|
79
|
+
# Make a copy of the current class's hooks for the subclass.
|
80
|
+
def inherited(subclass)
|
81
|
+
super
|
82
|
+
hooks = subclass.instance_variable_set(:@hooks, {})
|
83
|
+
instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup}
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Add a hook block to the list of hook methods.
|
89
|
+
# If a non-nil tag is given and it already is in the list of hooks,
|
90
|
+
# replace it with the new block.
|
91
|
+
def add_hook(hook, tag, &block)
|
92
|
+
unless block
|
93
|
+
(raise Error, 'No hook method specified') unless tag
|
94
|
+
block = proc {send tag}
|
95
|
+
end
|
96
|
+
h = @hooks[hook]
|
97
|
+
if tag && (old = h.find{|x| x[0] == tag})
|
98
|
+
old[1] = block
|
99
|
+
else
|
100
|
+
if hook.to_s =~ /^before/
|
101
|
+
h.unshift([tag,block])
|
102
|
+
else
|
103
|
+
h << [tag, block]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
module InstanceMethods
|
110
|
+
Model::HOOKS.each{|h| class_eval("def #{h}; run_hooks(:#{h}); end", __FILE__, __LINE__)}
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
# Runs all hook blocks of given hook type on this object.
|
115
|
+
# Stops running hook blocks and returns false if any hook block returns false.
|
116
|
+
def run_hooks(hook)
|
117
|
+
model.hook_blocks(hook){|block| return false if instance_eval(&block) == false}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
module Schema
|
4
|
+
module ClassMethods
|
5
|
+
# Creates table, using the column information from set_schema.
|
6
|
+
def create_table
|
7
|
+
db.create_table(table_name, :generator=>@schema)
|
8
|
+
@db_schema = get_db_schema(true)
|
9
|
+
columns
|
10
|
+
end
|
11
|
+
|
12
|
+
# Drops the table if it exists and then runs create_table. Should probably
|
13
|
+
# not be used except in testing.
|
14
|
+
def create_table!
|
15
|
+
drop_table rescue nil
|
16
|
+
create_table
|
17
|
+
end
|
18
|
+
|
19
|
+
# Drops table.
|
20
|
+
def drop_table
|
21
|
+
db.drop_table(table_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns table schema created with set_schema for direct descendant of Model.
|
25
|
+
# Does not retreive schema information from the database, see db_schema if you
|
26
|
+
# want that.
|
27
|
+
def schema
|
28
|
+
@schema || (superclass.schema unless superclass == Model)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Defines a table schema (see Schema::Generator for more information).
|
32
|
+
#
|
33
|
+
# This is only needed if you want to use the create_table/create_table! methods.
|
34
|
+
# Will also set the dataset if you provide a name, as well as setting
|
35
|
+
# the primary key if you defined one in the passed block.
|
36
|
+
#
|
37
|
+
# In general, it is a better idea to use migrations for production code, as
|
38
|
+
# migrations allow changes to existing schema. set_schema is mostly useful for
|
39
|
+
# test code or simple examples.
|
40
|
+
def set_schema(name = nil, &block)
|
41
|
+
set_dataset(db[name]) if name
|
42
|
+
@schema = Sequel::Schema::Generator.new(db, &block)
|
43
|
+
set_primary_key(@schema.primary_key_name) if @schema.primary_key_name
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns true if table exists, false otherwise.
|
47
|
+
def table_exists?
|
48
|
+
db.table_exists?(table_name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# Sequel's built in Serialization plugin allows you to keep serialized
|
4
|
+
# ruby objects in the database, while giving you deserialized objects
|
5
|
+
# when you call an accessor.
|
6
|
+
#
|
7
|
+
# This plugin works by keeping the serialized value in the values, and
|
8
|
+
# adding a @deserialized_values hash. The reader method for serialized columns
|
9
|
+
# will check the @deserialized_values for the value, return it if present,
|
10
|
+
# or deserialized the entry in @values and return it. The writer method will
|
11
|
+
# set the @deserialized_values entry. This plugin adds a before_save hook
|
12
|
+
# that serializes all @deserialized_values to @values.
|
13
|
+
#
|
14
|
+
# You can use either marshal or yaml as the serialization format.
|
15
|
+
# If you use yaml, you should require yaml yourself.
|
16
|
+
#
|
17
|
+
# Because of how this plugin works, it must be used inside each model class
|
18
|
+
# that needs serialization, after any set_dataset method calls in that class.
|
19
|
+
# Otherwise, it is possible that the default column accessors will take
|
20
|
+
# precedence.
|
21
|
+
module Serialization
|
22
|
+
# Set up the column readers to do deserialization and the column writers
|
23
|
+
# to save the value in deserialized_values.
|
24
|
+
def self.apply(model, format, *columns)
|
25
|
+
raise(Error, "Unsupported serialization format (#{format}), should be :marshal or :yaml") unless [:marshal, :yaml].include?(format)
|
26
|
+
raise(Error, "No columns given. The serialization plugin requires you specify which columns to serialize") if columns.empty?
|
27
|
+
model.instance_eval do
|
28
|
+
@serialization_format = format
|
29
|
+
@serialized_columns = columns
|
30
|
+
InstanceMethods.module_eval do
|
31
|
+
columns.each do |column|
|
32
|
+
define_method(column) do
|
33
|
+
if deserialized_values.has_key?(column)
|
34
|
+
deserialized_values[column]
|
35
|
+
else
|
36
|
+
deserialized_values[column] = deserialize_value(@values[column])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
define_method("#{column}=") do |v|
|
40
|
+
changed_columns << column unless changed_columns.include?(column)
|
41
|
+
deserialized_values[column] = v
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
# The serialization format to use, should be :marshal or :yaml
|
50
|
+
attr_reader :serialization_format
|
51
|
+
|
52
|
+
# The columns to serialize
|
53
|
+
attr_reader :serialized_columns
|
54
|
+
|
55
|
+
# Copy the serialization format and columns to serialize into the subclass.
|
56
|
+
def inherited(subclass)
|
57
|
+
super
|
58
|
+
sf = serialization_format
|
59
|
+
sc = serialized_columns
|
60
|
+
subclass.instance_eval do
|
61
|
+
@serialization_format = sf
|
62
|
+
@serialized_columns = sc
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module InstanceMethods
|
68
|
+
# Hash of deserialized values, used as a cache.
|
69
|
+
attr_reader :deserialized_values
|
70
|
+
|
71
|
+
# Set @deserialized_values to the empty hash
|
72
|
+
def initialize(*args, &block)
|
73
|
+
@deserialized_values = {}
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
# Serialize all deserialized values
|
78
|
+
def before_save
|
79
|
+
super
|
80
|
+
deserialized_values.each do |k,v|
|
81
|
+
@values[k] = serialize_value(v)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Empty the deserialized values when refreshing.
|
86
|
+
def refresh
|
87
|
+
@deserialized_values = {}
|
88
|
+
super
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Deserialize the column from either marshal or yaml format
|
94
|
+
def deserialize_value(v)
|
95
|
+
return v if v.nil?
|
96
|
+
case model.serialization_format
|
97
|
+
when :marshal
|
98
|
+
Marshal.load(v.unpack('m')[0]) rescue Marshal.load(v)
|
99
|
+
when :yaml
|
100
|
+
YAML.load v if v
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Serialize the column to either marshal or yaml format
|
105
|
+
def serialize_value(v)
|
106
|
+
return v if v.nil?
|
107
|
+
case model.serialization_format
|
108
|
+
when :marshal
|
109
|
+
[Marshal.dump(v)].pack('m')
|
110
|
+
when :yaml
|
111
|
+
v.to_yaml
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|