epugh-sequel 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +652 -0
- data/VERSION.yml +4 -0
- data/bin/sequel +104 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +85 -0
- data/lib/sequel/adapters/db2.rb +132 -0
- data/lib/sequel/adapters/dbi.rb +101 -0
- data/lib/sequel/adapters/do.rb +197 -0
- data/lib/sequel/adapters/do/mysql.rb +38 -0
- data/lib/sequel/adapters/do/postgres.rb +92 -0
- data/lib/sequel/adapters/do/sqlite.rb +31 -0
- data/lib/sequel/adapters/firebird.rb +307 -0
- data/lib/sequel/adapters/informix.rb +75 -0
- data/lib/sequel/adapters/jdbc.rb +485 -0
- data/lib/sequel/adapters/jdbc/h2.rb +62 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
- data/lib/sequel/adapters/mysql.rb +370 -0
- data/lib/sequel/adapters/odbc.rb +184 -0
- data/lib/sequel/adapters/openbase.rb +57 -0
- data/lib/sequel/adapters/oracle.rb +140 -0
- data/lib/sequel/adapters/postgres.rb +453 -0
- data/lib/sequel/adapters/shared/mssql.rb +93 -0
- data/lib/sequel/adapters/shared/mysql.rb +341 -0
- data/lib/sequel/adapters/shared/oracle.rb +62 -0
- data/lib/sequel/adapters/shared/postgres.rb +743 -0
- data/lib/sequel/adapters/shared/progress.rb +34 -0
- data/lib/sequel/adapters/shared/sqlite.rb +263 -0
- data/lib/sequel/adapters/sqlite.rb +243 -0
- data/lib/sequel/adapters/utils/date_format.rb +21 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
- data/lib/sequel/adapters/utils/unsupported.rb +62 -0
- data/lib/sequel/connection_pool.rb +258 -0
- data/lib/sequel/core.rb +204 -0
- data/lib/sequel/core_sql.rb +185 -0
- data/lib/sequel/database.rb +687 -0
- data/lib/sequel/database/schema_generator.rb +324 -0
- data/lib/sequel/database/schema_methods.rb +164 -0
- data/lib/sequel/database/schema_sql.rb +324 -0
- data/lib/sequel/dataset.rb +422 -0
- data/lib/sequel/dataset/convenience.rb +237 -0
- data/lib/sequel/dataset/prepared_statements.rb +220 -0
- data/lib/sequel/dataset/sql.rb +1105 -0
- data/lib/sequel/deprecated.rb +529 -0
- data/lib/sequel/exceptions.rb +44 -0
- data/lib/sequel/extensions/blank.rb +42 -0
- data/lib/sequel/extensions/inflector.rb +288 -0
- data/lib/sequel/extensions/pagination.rb +96 -0
- data/lib/sequel/extensions/pretty_table.rb +78 -0
- data/lib/sequel/extensions/query.rb +48 -0
- data/lib/sequel/extensions/string_date_time.rb +47 -0
- data/lib/sequel/metaprogramming.rb +44 -0
- data/lib/sequel/migration.rb +212 -0
- data/lib/sequel/model.rb +142 -0
- data/lib/sequel/model/association_reflection.rb +263 -0
- data/lib/sequel/model/associations.rb +1024 -0
- data/lib/sequel/model/base.rb +911 -0
- data/lib/sequel/model/deprecated.rb +188 -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 +384 -0
- data/lib/sequel/model/errors.rb +37 -0
- data/lib/sequel/model/exceptions.rb +7 -0
- data/lib/sequel/model/inflections.rb +230 -0
- data/lib/sequel/model/plugins.rb +74 -0
- data/lib/sequel/object_graph.rb +230 -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/single_table_inheritance.rb +63 -0
- data/lib/sequel/plugins/validation_class_methods.rb +373 -0
- data/lib/sequel/sql.rb +854 -0
- data/lib/sequel/version.rb +11 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/ado_spec.rb +46 -0
- data/spec/adapters/firebird_spec.rb +376 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +875 -0
- data/spec/adapters/oracle_spec.rb +272 -0
- data/spec/adapters/postgres_spec.rb +692 -0
- data/spec/adapters/spec_helper.rb +10 -0
- data/spec/adapters/sqlite_spec.rb +550 -0
- data/spec/core/connection_pool_spec.rb +526 -0
- data/spec/core/core_ext_spec.rb +156 -0
- data/spec/core/core_sql_spec.rb +528 -0
- data/spec/core/database_spec.rb +1214 -0
- data/spec/core/dataset_spec.rb +3513 -0
- data/spec/core/expression_filters_spec.rb +363 -0
- data/spec/core/migration_spec.rb +261 -0
- data/spec/core/object_graph_spec.rb +280 -0
- data/spec/core/pretty_table_spec.rb +58 -0
- data/spec/core/schema_generator_spec.rb +167 -0
- data/spec/core/schema_spec.rb +778 -0
- data/spec/core/spec_helper.rb +82 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/caching_spec.rb +201 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/inflector_spec.rb +122 -0
- 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/extensions/schema_spec.rb +111 -0
- data/spec/extensions/single_table_inheritance_spec.rb +53 -0
- data/spec/extensions/spec_helper.rb +90 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/validation_class_methods_spec.rb +1054 -0
- data/spec/integration/dataset_test.rb +160 -0
- data/spec/integration/eager_loader_test.rb +683 -0
- data/spec/integration/prepared_statement_test.rb +130 -0
- data/spec/integration/schema_test.rb +183 -0
- data/spec/integration/spec_helper.rb +75 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +93 -0
- data/spec/model/associations_spec.rb +1780 -0
- data/spec/model/base_spec.rb +494 -0
- data/spec/model/caching_spec.rb +217 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1165 -0
- data/spec/model/hooks_spec.rb +472 -0
- data/spec/model/inflector_spec.rb +126 -0
- data/spec/model/model_spec.rb +588 -0
- data/spec/model/plugins_spec.rb +142 -0
- data/spec/model/record_spec.rb +1243 -0
- data/spec/model/schema_spec.rb +92 -0
- data/spec/model/spec_helper.rb +124 -0
- data/spec/model/validations_spec.rb +1080 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +202 -0
@@ -0,0 +1,74 @@
|
|
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 has a DatasetMethods module and the model
|
20
|
+
# doesn't have a dataset, raise an Error.
|
21
|
+
def self.plugin(plugin, *args)
|
22
|
+
arg = args.first
|
23
|
+
block = lambda{arg}
|
24
|
+
m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
|
25
|
+
if m.respond_to?(:apply)
|
26
|
+
m.apply(self, *args)
|
27
|
+
end
|
28
|
+
if m.const_defined?("InstanceMethods")
|
29
|
+
define_method(:"#{plugin}_opts", &block)
|
30
|
+
include(m::InstanceMethods)
|
31
|
+
end
|
32
|
+
if m.const_defined?("ClassMethods")
|
33
|
+
meta_def(:"#{plugin}_opts", &block)
|
34
|
+
extend(m::ClassMethods)
|
35
|
+
end
|
36
|
+
if m.const_defined?("DatasetMethods")
|
37
|
+
if @dataset
|
38
|
+
dataset.meta_def(:"#{plugin}_opts", &block)
|
39
|
+
dataset.extend(m::DatasetMethods)
|
40
|
+
end
|
41
|
+
dataset_method_modules << m::DatasetMethods
|
42
|
+
def_dataset_method(*m::DatasetMethods.public_instance_methods.reject{|x| DATASET_METHOD_RE !~ x.to_s})
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
private
|
48
|
+
|
49
|
+
# Returns the new style location for the plugin name.
|
50
|
+
def plugin_gem_location(plugin)
|
51
|
+
"sequel/plugins/#{plugin}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the old style location for the plugin name.
|
55
|
+
def plugin_gem_location_old(plugin)
|
56
|
+
"sequel_#{plugin}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the module for the specified plugin. If the module is not
|
60
|
+
# defined, the corresponding plugin gem is automatically loaded.
|
61
|
+
def plugin_module(plugin)
|
62
|
+
module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
|
63
|
+
if not Sequel::Plugins.const_defined?(module_name)
|
64
|
+
begin
|
65
|
+
require plugin_gem_location(plugin)
|
66
|
+
rescue LoadError
|
67
|
+
require plugin_gem_location_old(plugin)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
Sequel::Plugins.const_get(module_name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
module Sequel
|
2
|
+
class Dataset
|
3
|
+
# Allows you to join multiple datasets/tables and have the result set
|
4
|
+
# split into component tables.
|
5
|
+
#
|
6
|
+
# This differs from the usual usage of join, which returns the result set
|
7
|
+
# as a single hash. For example:
|
8
|
+
#
|
9
|
+
# # CREATE TABLE artists (id INTEGER, name TEXT);
|
10
|
+
# # CREATE TABLE albums (id INTEGER, name TEXT, artist_id INTEGER);
|
11
|
+
# DB[:artists].left_outer_join(:albums, :artist_id=>:id).first
|
12
|
+
# => {:id=>(albums.id||artists.id), :name=>(albums.name||artist.names), :artist_id=>albums.artist_id}
|
13
|
+
# DB[:artists].graph(:albums, :artist_id=>:id).first
|
14
|
+
# => {:artists=>{:id=>artists.id, :name=>artists.name}, :albums=>{:id=>albums.id, :name=>albums.name, :artist_id=>albums.artist_id}}
|
15
|
+
#
|
16
|
+
# Using a join such as left_outer_join, the attribute names that are shared between
|
17
|
+
# the tables are combined in the single return hash. You can get around that by
|
18
|
+
# using .select with correct aliases for all of the columns, but it is simpler to
|
19
|
+
# use graph and have the result set split for you. In addition, graph respects
|
20
|
+
# any row_proc or transform attributes of the current dataset and the datasets
|
21
|
+
# you use with graph.
|
22
|
+
#
|
23
|
+
# If you are graphing a table and all columns for that table are nil, this
|
24
|
+
# indicates that no matching rows existed in the table, so graph will return nil
|
25
|
+
# instead of a hash with all nil values:
|
26
|
+
#
|
27
|
+
# # If the artist doesn't have any albums
|
28
|
+
# DB[:artists].graph(:albums, :artist_id=>:id).first
|
29
|
+
# => {:artists=>{:id=>artists.id, :name=>artists.name}, :albums=>nil}
|
30
|
+
#
|
31
|
+
# Arguments:
|
32
|
+
# * dataset - Can be a symbol (specifying a table), another dataset,
|
33
|
+
# or an object that responds to .dataset and yields a symbol or a dataset
|
34
|
+
# * join_conditions - Any condition(s) allowed by join_table.
|
35
|
+
# * options - A hash of graph options. The following options are currently used:
|
36
|
+
# * :implicit_qualifier - The qualifier of implicit conditions, see #join_table.
|
37
|
+
# * :join_type - The type of join to use (passed to join_table). Defaults to
|
38
|
+
# :left_outer.
|
39
|
+
# * :select - An array of columns to select. When not used, selects
|
40
|
+
# all columns in the given dataset. When set to false, selects no
|
41
|
+
# columns and is like simply joining the tables, though graph keeps
|
42
|
+
# some metadata about join that makes it important to use graph instead
|
43
|
+
# of join.
|
44
|
+
# * :table_alias - The alias to use for the table. If not specified, doesn't
|
45
|
+
# alias the table. You will get an error if the the alias (or table) name is
|
46
|
+
# used more than once.
|
47
|
+
# * block - A block that is passed to join_table.
|
48
|
+
def graph(dataset, join_conditions = nil, options = {}, &block)
|
49
|
+
# Allow the use of a model, dataset, or symbol as the first argument
|
50
|
+
# Find the table name/dataset based on the argument
|
51
|
+
dataset = dataset.dataset if dataset.respond_to?(:dataset)
|
52
|
+
case dataset
|
53
|
+
when Symbol
|
54
|
+
table = dataset
|
55
|
+
dataset = @db[dataset]
|
56
|
+
when ::Sequel::Dataset
|
57
|
+
table = dataset.first_source
|
58
|
+
else
|
59
|
+
raise Error, "The dataset argument should be a symbol, dataset, or model"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Raise Sequel::Error with explanation that the table alias has been used
|
63
|
+
raise_alias_error = lambda do
|
64
|
+
raise(Error, "this #{options[:table_alias] ? 'alias' : 'table'} has already been been used, please specify " \
|
65
|
+
"#{options[:table_alias] ? 'a different alias' : 'an alias via the :table_alias option'}")
|
66
|
+
end
|
67
|
+
|
68
|
+
# Only allow table aliases that haven't been used
|
69
|
+
table_alias = options[:table_alias] || table
|
70
|
+
raise_alias_error.call if @opts[:graph] && @opts[:graph][:table_aliases] && @opts[:graph][:table_aliases].include?(table_alias)
|
71
|
+
|
72
|
+
# Join the table early in order to avoid cloning the dataset twice
|
73
|
+
ds = join_table(options[:join_type] || :left_outer, table, join_conditions, :table_alias=>table_alias, :implicit_qualifier=>options[:implicit_qualifier], &block)
|
74
|
+
opts = ds.opts
|
75
|
+
|
76
|
+
# Whether to include the table in the result set
|
77
|
+
add_table = options[:select] == false ? false : true
|
78
|
+
# Whether to add the columns to the list of column aliases
|
79
|
+
add_columns = !ds.opts.include?(:graph_aliases)
|
80
|
+
|
81
|
+
# Setup the initial graph data structure if it doesn't exist
|
82
|
+
unless graph = opts[:graph]
|
83
|
+
master = ds.first_source
|
84
|
+
raise_alias_error.call if master == table_alias
|
85
|
+
# Master hash storing all .graph related information
|
86
|
+
graph = opts[:graph] = {}
|
87
|
+
# Associates column aliases back to tables and columns
|
88
|
+
column_aliases = graph[:column_aliases] = {}
|
89
|
+
# Associates table alias (the master is never aliased)
|
90
|
+
table_aliases = graph[:table_aliases] = {master=>self}
|
91
|
+
# Keep track of the alias numbers used
|
92
|
+
ca_num = graph[:column_alias_num] = Hash.new(0)
|
93
|
+
# All columns in the master table are never
|
94
|
+
# aliased, but are not included if set_graph_aliases
|
95
|
+
# has been used.
|
96
|
+
if add_columns
|
97
|
+
select = opts[:select] = []
|
98
|
+
columns.each do |column|
|
99
|
+
column_aliases[column] = [master, column]
|
100
|
+
select.push(column.qualify(master))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Add the table alias to the list of aliases
|
106
|
+
# Even if it isn't been used in the result set,
|
107
|
+
# we add a key for it with a nil value so we can check if it
|
108
|
+
# is used more than once
|
109
|
+
table_aliases = graph[:table_aliases]
|
110
|
+
table_aliases[table_alias] = add_table ? dataset : nil
|
111
|
+
|
112
|
+
# Add the columns to the selection unless we are ignoring them
|
113
|
+
if add_table && add_columns
|
114
|
+
select = opts[:select]
|
115
|
+
column_aliases = graph[:column_aliases]
|
116
|
+
ca_num = graph[:column_alias_num]
|
117
|
+
# Which columns to add to the result set
|
118
|
+
cols = options[:select] || dataset.columns
|
119
|
+
# If the column hasn't been used yet, don't alias it.
|
120
|
+
# If it has been used, try table_column.
|
121
|
+
# If that has been used, try table_column_N
|
122
|
+
# using the next value of N that we know hasn't been
|
123
|
+
# used
|
124
|
+
cols.each do |column|
|
125
|
+
col_alias, identifier = if column_aliases[column]
|
126
|
+
column_alias = :"#{table_alias}_#{column}"
|
127
|
+
if column_aliases[column_alias]
|
128
|
+
column_alias_num = ca_num[column_alias]
|
129
|
+
column_alias = :"#{column_alias}_#{column_alias_num}"
|
130
|
+
ca_num[column_alias] += 1
|
131
|
+
end
|
132
|
+
[column_alias, column.qualify(table_alias).as(column_alias)]
|
133
|
+
else
|
134
|
+
[column, column.qualify(table_alias)]
|
135
|
+
end
|
136
|
+
column_aliases[col_alias] = [table_alias, column]
|
137
|
+
select.push(identifier)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
ds
|
141
|
+
end
|
142
|
+
|
143
|
+
# This allows you to manually specify the graph aliases to use
|
144
|
+
# when using graph. You can use it to only select certain
|
145
|
+
# columns, and have those columns mapped to specific aliases
|
146
|
+
# in the result set. This is the equivalent of .select for a
|
147
|
+
# graphed dataset, and must be used instead of .select whenever
|
148
|
+
# graphing is used. Example:
|
149
|
+
#
|
150
|
+
# DB[:artists].graph(:albums, :artist_id=>:id).set_graph_aliases(:artist_name=>[:artists, :name], :album_name=>[:albums, :name]).first
|
151
|
+
# => {:artists=>{:name=>artists.name}, :albums=>{:name=>albums.name}}
|
152
|
+
#
|
153
|
+
# Arguments:
|
154
|
+
# * graph_aliases - Should be a hash with keys being symbols of
|
155
|
+
# column aliases, and values being arrays with two symbol elements.
|
156
|
+
# The first element of the array should be the table alias,
|
157
|
+
# and the second should be the actual column name.
|
158
|
+
def set_graph_aliases(graph_aliases)
|
159
|
+
ds = select(*graph_alias_columns(graph_aliases))
|
160
|
+
ds.opts[:graph_aliases] = graph_aliases
|
161
|
+
ds
|
162
|
+
end
|
163
|
+
|
164
|
+
# Adds the give graph aliases to the list of graph aliases to use,
|
165
|
+
# unlike #set_graph_aliases, which replaces the list. See
|
166
|
+
# #set_graph_aliases.
|
167
|
+
def add_graph_aliases(graph_aliases)
|
168
|
+
ds = select_more(*graph_alias_columns(graph_aliases))
|
169
|
+
ds.opts[:graph_aliases] = (ds.opts[:graph_aliases] || {}).merge(graph_aliases)
|
170
|
+
ds
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
# Transform the hash of graph aliases to an array of columns
|
176
|
+
def graph_alias_columns(graph_aliases)
|
177
|
+
graph_aliases.collect do |col_alias, tc|
|
178
|
+
identifier = tc[2] || tc[1].qualify(tc[0])
|
179
|
+
identifier = SQL::AliasedExpression.new(identifier, col_alias) if tc[2] or tc[1] != col_alias
|
180
|
+
identifier
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Fetch the rows, split them into component table parts,
|
185
|
+
# tranform and run the row_proc on each part (if applicable),
|
186
|
+
# and yield a hash of the parts.
|
187
|
+
def graph_each(opts=(defarg=true;nil), &block)
|
188
|
+
# Reject tables with nil datasets, as they are excluded from
|
189
|
+
# the result set
|
190
|
+
datasets = @opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
|
191
|
+
# Get just the list of table aliases into a local variable, for speed
|
192
|
+
table_aliases = datasets.collect{|ta,ds| ta}
|
193
|
+
# Get an array of arrays, one for each dataset, with
|
194
|
+
# the necessary information about each dataset, for speed
|
195
|
+
datasets = datasets.collect do |ta, ds|
|
196
|
+
[ta, ds, ds.instance_variable_get(:@transform), ds.row_proc]
|
197
|
+
end
|
198
|
+
# Use the manually set graph aliases, if any, otherwise
|
199
|
+
# use the ones automatically created by .graph
|
200
|
+
column_aliases = @opts[:graph_aliases] || @opts[:graph][:column_aliases]
|
201
|
+
fetch_rows(defarg ? select_sql : select_sql(opts)) do |r|
|
202
|
+
graph = {}
|
203
|
+
# Create the sub hashes, one per table
|
204
|
+
table_aliases.each{|ta| graph[ta]={}}
|
205
|
+
# Split the result set based on the column aliases
|
206
|
+
# If there are columns in the result set that are
|
207
|
+
# not in column_aliases, they are ignored
|
208
|
+
column_aliases.each do |col_alias, tc|
|
209
|
+
ta, column = tc
|
210
|
+
graph[ta][column] = r[col_alias]
|
211
|
+
end
|
212
|
+
# For each dataset, transform and run the row
|
213
|
+
# row_proc if applicable
|
214
|
+
datasets.each do |ta,ds,tr,rp|
|
215
|
+
g = graph[ta]
|
216
|
+
graph[ta] = if g.values.any?{|x| !x.nil?}
|
217
|
+
g = ds.transform_load(g) if tr
|
218
|
+
g = rp[g] if rp
|
219
|
+
g
|
220
|
+
else
|
221
|
+
nil
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
yield graph
|
226
|
+
end
|
227
|
+
self
|
228
|
+
end
|
229
|
+
end
|
230
|
+
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
|