viking-sequel 3.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3134 -0
- data/COPYING +19 -0
- data/README.rdoc +723 -0
- data/Rakefile +193 -0
- data/bin/sequel +196 -0
- data/doc/advanced_associations.rdoc +644 -0
- data/doc/cheat_sheet.rdoc +218 -0
- data/doc/dataset_basics.rdoc +106 -0
- data/doc/dataset_filtering.rdoc +158 -0
- data/doc/opening_databases.rdoc +296 -0
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/reflection.rdoc +84 -0
- data/doc/release_notes/1.0.txt +38 -0
- data/doc/release_notes/1.1.txt +143 -0
- data/doc/release_notes/1.3.txt +101 -0
- data/doc/release_notes/1.4.0.txt +53 -0
- data/doc/release_notes/1.5.0.txt +155 -0
- data/doc/release_notes/2.0.0.txt +298 -0
- data/doc/release_notes/2.1.0.txt +271 -0
- data/doc/release_notes/2.10.0.txt +328 -0
- data/doc/release_notes/2.11.0.txt +215 -0
- data/doc/release_notes/2.12.0.txt +534 -0
- data/doc/release_notes/2.2.0.txt +253 -0
- data/doc/release_notes/2.3.0.txt +88 -0
- data/doc/release_notes/2.4.0.txt +106 -0
- data/doc/release_notes/2.5.0.txt +137 -0
- data/doc/release_notes/2.6.0.txt +157 -0
- data/doc/release_notes/2.7.0.txt +166 -0
- data/doc/release_notes/2.8.0.txt +171 -0
- data/doc/release_notes/2.9.0.txt +97 -0
- data/doc/release_notes/3.0.0.txt +221 -0
- data/doc/release_notes/3.1.0.txt +406 -0
- data/doc/release_notes/3.10.0.txt +286 -0
- data/doc/release_notes/3.2.0.txt +268 -0
- data/doc/release_notes/3.3.0.txt +192 -0
- data/doc/release_notes/3.4.0.txt +325 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/release_notes/3.7.0.txt +179 -0
- data/doc/release_notes/3.8.0.txt +151 -0
- data/doc/release_notes/3.9.0.txt +233 -0
- data/doc/schema.rdoc +36 -0
- data/doc/sharding.rdoc +113 -0
- data/doc/virtual_rows.rdoc +205 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +90 -0
- data/lib/sequel/adapters/ado/mssql.rb +30 -0
- data/lib/sequel/adapters/amalgalite.rb +176 -0
- data/lib/sequel/adapters/db2.rb +139 -0
- data/lib/sequel/adapters/dbi.rb +113 -0
- data/lib/sequel/adapters/do.rb +188 -0
- data/lib/sequel/adapters/do/mysql.rb +49 -0
- data/lib/sequel/adapters/do/postgres.rb +91 -0
- data/lib/sequel/adapters/do/sqlite.rb +40 -0
- data/lib/sequel/adapters/firebird.rb +283 -0
- data/lib/sequel/adapters/informix.rb +77 -0
- data/lib/sequel/adapters/jdbc.rb +587 -0
- data/lib/sequel/adapters/jdbc/as400.rb +58 -0
- data/lib/sequel/adapters/jdbc/h2.rb +133 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
- data/lib/sequel/adapters/mysql.rb +421 -0
- data/lib/sequel/adapters/odbc.rb +143 -0
- data/lib/sequel/adapters/odbc/mssql.rb +42 -0
- data/lib/sequel/adapters/openbase.rb +64 -0
- data/lib/sequel/adapters/oracle.rb +131 -0
- data/lib/sequel/adapters/postgres.rb +504 -0
- data/lib/sequel/adapters/shared/mssql.rb +490 -0
- data/lib/sequel/adapters/shared/mysql.rb +498 -0
- data/lib/sequel/adapters/shared/oracle.rb +195 -0
- data/lib/sequel/adapters/shared/postgres.rb +830 -0
- data/lib/sequel/adapters/shared/progress.rb +44 -0
- data/lib/sequel/adapters/shared/sqlite.rb +389 -0
- data/lib/sequel/adapters/sqlite.rb +224 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
- data/lib/sequel/connection_pool.rb +99 -0
- data/lib/sequel/connection_pool/sharded_single.rb +84 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
- data/lib/sequel/connection_pool/single.rb +29 -0
- data/lib/sequel/connection_pool/threaded.rb +150 -0
- data/lib/sequel/core.rb +293 -0
- data/lib/sequel/core_sql.rb +241 -0
- data/lib/sequel/database.rb +1079 -0
- data/lib/sequel/database/schema_generator.rb +327 -0
- data/lib/sequel/database/schema_methods.rb +203 -0
- data/lib/sequel/database/schema_sql.rb +320 -0
- data/lib/sequel/dataset.rb +32 -0
- data/lib/sequel/dataset/actions.rb +441 -0
- data/lib/sequel/dataset/features.rb +86 -0
- data/lib/sequel/dataset/graph.rb +254 -0
- data/lib/sequel/dataset/misc.rb +119 -0
- data/lib/sequel/dataset/mutation.rb +64 -0
- data/lib/sequel/dataset/prepared_statements.rb +227 -0
- data/lib/sequel/dataset/query.rb +709 -0
- data/lib/sequel/dataset/sql.rb +996 -0
- data/lib/sequel/exceptions.rb +51 -0
- data/lib/sequel/extensions/blank.rb +43 -0
- data/lib/sequel/extensions/inflector.rb +242 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/migration.rb +239 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/pagination.rb +100 -0
- data/lib/sequel/extensions/pretty_table.rb +82 -0
- data/lib/sequel/extensions/query.rb +52 -0
- data/lib/sequel/extensions/schema_dumper.rb +271 -0
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +46 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/metaprogramming.rb +9 -0
- data/lib/sequel/model.rb +120 -0
- data/lib/sequel/model/associations.rb +1514 -0
- data/lib/sequel/model/base.rb +1069 -0
- data/lib/sequel/model/default_inflections.rb +45 -0
- data/lib/sequel/model/errors.rb +39 -0
- data/lib/sequel/model/exceptions.rb +21 -0
- data/lib/sequel/model/inflections.rb +162 -0
- data/lib/sequel/model/plugins.rb +70 -0
- data/lib/sequel/plugins/active_model.rb +59 -0
- data/lib/sequel/plugins/association_dependencies.rb +103 -0
- data/lib/sequel/plugins/association_proxies.rb +41 -0
- data/lib/sequel/plugins/boolean_readers.rb +53 -0
- data/lib/sequel/plugins/caching.rb +141 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/composition.rb +138 -0
- data/lib/sequel/plugins/force_encoding.rb +72 -0
- data/lib/sequel/plugins/hook_class_methods.rb +126 -0
- data/lib/sequel/plugins/identity_map.rb +116 -0
- data/lib/sequel/plugins/instance_filters.rb +98 -0
- data/lib/sequel/plugins/instance_hooks.rb +57 -0
- data/lib/sequel/plugins/lazy_attributes.rb +77 -0
- data/lib/sequel/plugins/many_through_many.rb +208 -0
- data/lib/sequel/plugins/nested_attributes.rb +206 -0
- data/lib/sequel/plugins/optimistic_locking.rb +81 -0
- data/lib/sequel/plugins/rcte_tree.rb +281 -0
- data/lib/sequel/plugins/schema.rb +66 -0
- data/lib/sequel/plugins/serialization.rb +166 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
- data/lib/sequel/plugins/timestamps.rb +87 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +72 -0
- data/lib/sequel/plugins/validation_class_methods.rb +405 -0
- data/lib/sequel/plugins/validation_helpers.rb +223 -0
- data/lib/sequel/sql.rb +1020 -0
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +12 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/firebird_spec.rb +407 -0
- data/spec/adapters/informix_spec.rb +97 -0
- data/spec/adapters/mssql_spec.rb +403 -0
- data/spec/adapters/mysql_spec.rb +1019 -0
- data/spec/adapters/oracle_spec.rb +286 -0
- data/spec/adapters/postgres_spec.rb +969 -0
- data/spec/adapters/spec_helper.rb +51 -0
- data/spec/adapters/sqlite_spec.rb +432 -0
- data/spec/core/connection_pool_spec.rb +808 -0
- data/spec/core/core_sql_spec.rb +417 -0
- data/spec/core/database_spec.rb +1662 -0
- data/spec/core/dataset_spec.rb +3827 -0
- data/spec/core/expression_filters_spec.rb +595 -0
- data/spec/core/object_graph_spec.rb +296 -0
- data/spec/core/schema_generator_spec.rb +159 -0
- data/spec/core/schema_spec.rb +830 -0
- data/spec/core/spec_helper.rb +56 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/active_model_spec.rb +76 -0
- data/spec/extensions/association_dependencies_spec.rb +127 -0
- data/spec/extensions/association_proxies_spec.rb +50 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/boolean_readers_spec.rb +92 -0
- data/spec/extensions/caching_spec.rb +250 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/composition_spec.rb +194 -0
- data/spec/extensions/force_encoding_spec.rb +117 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/identity_map_spec.rb +202 -0
- data/spec/extensions/inflector_spec.rb +181 -0
- data/spec/extensions/instance_filters_spec.rb +55 -0
- data/spec/extensions/instance_hooks_spec.rb +133 -0
- data/spec/extensions/lazy_attributes_spec.rb +153 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +884 -0
- data/spec/extensions/migration_spec.rb +332 -0
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +396 -0
- data/spec/extensions/optimistic_locking_spec.rb +100 -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/rcte_tree_spec.rb +205 -0
- data/spec/extensions/schema_dumper_spec.rb +357 -0
- data/spec/extensions/schema_spec.rb +127 -0
- data/spec/extensions/serialization_spec.rb +209 -0
- data/spec/extensions/single_table_inheritance_spec.rb +96 -0
- data/spec/extensions/spec_helper.rb +91 -0
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/timestamps_spec.rb +150 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +69 -0
- data/spec/extensions/validation_class_methods_spec.rb +984 -0
- data/spec/extensions/validation_helpers_spec.rb +438 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/database_test.rb +26 -0
- data/spec/integration/dataset_test.rb +963 -0
- data/spec/integration/eager_loader_test.rb +734 -0
- data/spec/integration/model_test.rb +130 -0
- data/spec/integration/plugin_test.rb +814 -0
- data/spec/integration/prepared_statement_test.rb +213 -0
- data/spec/integration/schema_test.rb +361 -0
- data/spec/integration/spec_helper.rb +73 -0
- data/spec/integration/timezone_test.rb +55 -0
- data/spec/integration/transaction_test.rb +122 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +175 -0
- data/spec/model/associations_spec.rb +2633 -0
- data/spec/model/base_spec.rb +418 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1391 -0
- data/spec/model/hooks_spec.rb +240 -0
- data/spec/model/inflector_spec.rb +26 -0
- data/spec/model/model_spec.rb +593 -0
- data/spec/model/plugins_spec.rb +236 -0
- data/spec/model/record_spec.rb +1500 -0
- data/spec/model/spec_helper.rb +97 -0
- data/spec/model/validations_spec.rb +153 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +346 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The composition plugin allows you to easily define getter and
|
4
|
+
# setter instance methods for a class where the backing data
|
5
|
+
# is composed of other getters and decomposed to other setters.
|
6
|
+
#
|
7
|
+
# A simple example of this is when you have a database table with
|
8
|
+
# separate columns for year, month, and day, but where you want
|
9
|
+
# to deal with Date objects in your ruby code. This can be handled
|
10
|
+
# with:
|
11
|
+
#
|
12
|
+
# Model.composition :date, :mapping=>[:year, :month, :day]
|
13
|
+
#
|
14
|
+
# The :mapping option is optional, but you can define custom
|
15
|
+
# composition and decomposition procs via the :composer and
|
16
|
+
# :decomposer options.
|
17
|
+
#
|
18
|
+
# Note that when using the composition object, you should not
|
19
|
+
# modify the underlying columns if you are also instantiating
|
20
|
+
# the composition, as otherwise the composition object values
|
21
|
+
# will override any underlying columns when the object is saved.
|
22
|
+
module Composition
|
23
|
+
# Define the necessary class instance variables.
|
24
|
+
def self.apply(model)
|
25
|
+
model.instance_eval{@compositions = {}}
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
# A hash with composition name keys and composition reflection
|
30
|
+
# hash values.
|
31
|
+
attr_reader :compositions
|
32
|
+
|
33
|
+
# A module included in the class holding the composition
|
34
|
+
# getter and setter methods.
|
35
|
+
attr_reader :composition_module
|
36
|
+
|
37
|
+
# Define a composition for this model, with name being the name of the composition.
|
38
|
+
# You must provide either a :mapping option or both the :composer and :decomposer options.
|
39
|
+
#
|
40
|
+
# Options:
|
41
|
+
# * :class - if using the :mapping option, the class to use, as a Class, String or Symbol.
|
42
|
+
# * :composer - A proc that is instance evaled when the composition getter method is called
|
43
|
+
# to create the composition.
|
44
|
+
# * :decomposer - A proc that is instance evaled before saving the model object,
|
45
|
+
# if the composition object exists, which sets the columns in the model object
|
46
|
+
# based on the value of the composition object.
|
47
|
+
# * :mapping - An array where each element is either a symbol or an array of two symbols.
|
48
|
+
# A symbol is treated like an array of two symbols where both symbols are the same.
|
49
|
+
# The first symbol represents the getter method in the model, and the second symbol
|
50
|
+
# represents the getter method in the composition object. Example:
|
51
|
+
# # Uses columns year, month, and day in the current model
|
52
|
+
# # Uses year, month, and day methods in the composition object
|
53
|
+
# :mapping=>[:year, :month, :day]
|
54
|
+
# # Uses columns year, month, and day in the current model
|
55
|
+
# # Uses y, m, and d methods in the composition object where
|
56
|
+
# # for example y in the composition object represents year
|
57
|
+
# # in the model object.
|
58
|
+
# :mapping=>[[:year, :y], [:month, :m], [:day, :d]]
|
59
|
+
def composition(name, opts={})
|
60
|
+
opts = opts.dup
|
61
|
+
compositions[name] = opts
|
62
|
+
if mapping = opts[:mapping]
|
63
|
+
keys = mapping.map{|k| k.is_a?(Array) ? k.first : k}
|
64
|
+
if !opts[:composer]
|
65
|
+
late_binding_class_option(opts, name)
|
66
|
+
klass = opts[:class]
|
67
|
+
class_proc = proc{klass || constantize(opts[:class_name])}
|
68
|
+
opts[:composer] = proc do
|
69
|
+
if values = keys.map{|k| send(k)} and values.any?{|v| !v.nil?}
|
70
|
+
class_proc.call.new(*values)
|
71
|
+
else
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
if !opts[:decomposer]
|
77
|
+
setter_meths = keys.map{|k| :"#{k}="}
|
78
|
+
cov_methods = mapping.map{|k| k.is_a?(Array) ? k.last : k}
|
79
|
+
setters = setter_meths.zip(cov_methods)
|
80
|
+
opts[:decomposer] = proc do
|
81
|
+
if (o = compositions[name]).nil?
|
82
|
+
setter_meths.each{|sm| send(sm, nil)}
|
83
|
+
else
|
84
|
+
setters.each{|sm, cm| send(sm, o.send(cm))}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
raise(Error, "Must provide :composer and :decomposer options, or :mapping option") unless opts[:composer] && opts[:decomposer]
|
90
|
+
define_composition_accessor(name, opts)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Copy the necessary class instance variables to the subclass.
|
94
|
+
def inherited(subclass)
|
95
|
+
super
|
96
|
+
c = compositions.dup
|
97
|
+
subclass.instance_eval{@compositions = c}
|
98
|
+
end
|
99
|
+
|
100
|
+
# Define getter and setter methods for the composition object.
|
101
|
+
def define_composition_accessor(name, opts={})
|
102
|
+
include(@composition_module ||= Module.new) unless composition_module
|
103
|
+
composer = opts[:composer]
|
104
|
+
composition_module.class_eval do
|
105
|
+
define_method(name) do
|
106
|
+
compositions.include?(name) ? compositions[name] : (compositions[name] = instance_eval(&composer))
|
107
|
+
end
|
108
|
+
define_method("#{name}=") do |v|
|
109
|
+
modified!
|
110
|
+
compositions[name] = v
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module InstanceMethods
|
117
|
+
# Clear the cached compositions when refreshing.
|
118
|
+
def _refresh(ds)
|
119
|
+
v = super
|
120
|
+
compositions.clear
|
121
|
+
v
|
122
|
+
end
|
123
|
+
|
124
|
+
# For each composition, set the columns in the model class based
|
125
|
+
# on the composition object.
|
126
|
+
def before_save
|
127
|
+
@compositions.keys.each{|n| instance_eval(&model.compositions[n][:decomposer])} if @compositions
|
128
|
+
super
|
129
|
+
end
|
130
|
+
|
131
|
+
# Cache of composition objects for this class.
|
132
|
+
def compositions
|
133
|
+
@compositions ||= {}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
if RUBY_VERSION >= '1.9.0'
|
2
|
+
module Sequel
|
3
|
+
module Plugins
|
4
|
+
# The ForceEncoding plugin allows you force specific encodings for all
|
5
|
+
# strings that are used by the model. When model instances are loaded
|
6
|
+
# from the database, all values in the hash that are strings are
|
7
|
+
# forced to the given encoding. Whenever you update a model column
|
8
|
+
# attribute, the resulting value is forced to a given encoding if the
|
9
|
+
# value is a string. There are two ways to specify the encoding. You
|
10
|
+
# can either do so in the plugin call itself, or via the
|
11
|
+
# forced_encoding class accessor:
|
12
|
+
#
|
13
|
+
# class Album < Sequel::Model
|
14
|
+
# plugin :force_encoding, 'UTF-8'
|
15
|
+
# # or
|
16
|
+
# plugin :force_encoding
|
17
|
+
# self.forced_encoding = 'UTF-8'
|
18
|
+
# end
|
19
|
+
module ForceEncoding
|
20
|
+
# Set the forced_encoding based on the value given in the plugin call.
|
21
|
+
# Note that if a the plugin has been previously loaded, any previous
|
22
|
+
# forced encoding is overruled, even if no encoding is given when calling
|
23
|
+
# the plugin.
|
24
|
+
def self.configure(model, encoding=nil)
|
25
|
+
model.forced_encoding = encoding
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
# The string encoding to force on a column string values
|
30
|
+
attr_accessor :forced_encoding
|
31
|
+
|
32
|
+
# Copy the forced_encoding value into the subclass
|
33
|
+
def inherited(subclass)
|
34
|
+
super
|
35
|
+
subclass.forced_encoding = forced_encoding
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
# Allow the force encoding plugin to work with the identity_map
|
41
|
+
# plugin by typecasting new values.
|
42
|
+
def merge_db_update(row)
|
43
|
+
super(force_hash_encoding(row))
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Force the encoding for all string values in the given row hash.
|
49
|
+
def force_hash_encoding(row)
|
50
|
+
fe = model.forced_encoding
|
51
|
+
row.values.each{|v| v.force_encoding(fe) if v.is_a?(String)} if fe
|
52
|
+
row
|
53
|
+
end
|
54
|
+
|
55
|
+
# Force the encoding of all string values when setting the instance's values.
|
56
|
+
def set_values(row)
|
57
|
+
super(force_hash_encoding(row))
|
58
|
+
end
|
59
|
+
|
60
|
+
# Force the encoding of all returned strings to the model's forced_encoding.
|
61
|
+
def typecast_value(column, value)
|
62
|
+
s = super
|
63
|
+
s.force_encoding(model.forced_encoding) if s.is_a?(String) && model.forced_encoding
|
64
|
+
s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
else
|
71
|
+
raise LoadError, 'ForceEncoding plugin only works on Ruby 1.9+'
|
72
|
+
end
|
@@ -0,0 +1,126 @@
|
|
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
|
+
#
|
23
|
+
# Note that returning false in any before hook block will skip further
|
24
|
+
# before hooks and abort the action. So if a before_save hook block returns
|
25
|
+
# false, future before_save hook blocks are not called, and the save is aborted.
|
26
|
+
module HookClassMethods
|
27
|
+
# Set up the hooks instance variable in the model.
|
28
|
+
def self.apply(model)
|
29
|
+
hooks = model.instance_variable_set(:@hooks, {})
|
30
|
+
Model::HOOKS.each{|h| hooks[h] = []}
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
Model::HOOKS.each{|h| class_eval("def #{h}(method = nil, &block); add_hook(:#{h}, method, &block) end", __FILE__, __LINE__)}
|
35
|
+
|
36
|
+
# This adds a new hook type. It will define both a class
|
37
|
+
# method that you can use to add hooks, as well as an instance method
|
38
|
+
# that you can use to call all hooks of that type. The class method
|
39
|
+
# can be called with a symbol or a block or both. If a block is given and
|
40
|
+
# and symbol is not, it adds the hook block to the hook type. If a block
|
41
|
+
# and symbol are both given, it replaces the hook block associated with
|
42
|
+
# that symbol for a given hook type, or adds it if there is no hook block
|
43
|
+
# with that symbol for that hook type. If no block is given, it assumes
|
44
|
+
# the symbol specifies an instance method to call and adds it to the hook
|
45
|
+
# type.
|
46
|
+
#
|
47
|
+
# If any hook block returns false, the instance method will return false
|
48
|
+
# immediately without running the rest of the hooks of that type.
|
49
|
+
#
|
50
|
+
# It is recommended that you always provide a symbol to this method,
|
51
|
+
# for descriptive purposes. It's only necessary to do so when you
|
52
|
+
# are using a system that reloads code.
|
53
|
+
#
|
54
|
+
# Example of usage:
|
55
|
+
#
|
56
|
+
# class MyModel
|
57
|
+
# define_hook :before_move_to
|
58
|
+
# before_move_to(:check_move_allowed){|o| o.allow_move?}
|
59
|
+
# def move_to(there)
|
60
|
+
# return if before_move_to == false
|
61
|
+
# # move MyModel object to there
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
def add_hook_type(*hooks)
|
65
|
+
Model::HOOKS.concat(hooks)
|
66
|
+
hooks.each do |hook|
|
67
|
+
@hooks[hook] = []
|
68
|
+
instance_eval("def #{hook}(method = nil, &block); add_hook(:#{hook}, method, &block) end", __FILE__, __LINE__)
|
69
|
+
class_eval("def #{hook}; run_hooks(:#{hook}); end", __FILE__, __LINE__)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns true if there are any hook blocks for the given hook.
|
74
|
+
def has_hooks?(hook)
|
75
|
+
!@hooks[hook].empty?
|
76
|
+
end
|
77
|
+
|
78
|
+
# Yield every block related to the given hook.
|
79
|
+
def hook_blocks(hook)
|
80
|
+
@hooks[hook].each{|k,v| yield v}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Make a copy of the current class's hooks for the subclass.
|
84
|
+
def inherited(subclass)
|
85
|
+
super
|
86
|
+
hooks = subclass.instance_variable_set(:@hooks, {})
|
87
|
+
instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup}
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Add a hook block to the list of hook methods.
|
93
|
+
# If a non-nil tag is given and it already is in the list of hooks,
|
94
|
+
# replace it with the new block.
|
95
|
+
def add_hook(hook, tag, &block)
|
96
|
+
unless block
|
97
|
+
(raise Error, 'No hook method specified') unless tag
|
98
|
+
block = proc {send tag}
|
99
|
+
end
|
100
|
+
h = @hooks[hook]
|
101
|
+
if tag && (old = h.find{|x| x[0] == tag})
|
102
|
+
old[1] = block
|
103
|
+
else
|
104
|
+
if hook.to_s =~ /^before/
|
105
|
+
h.unshift([tag,block])
|
106
|
+
else
|
107
|
+
h << [tag, block]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
module InstanceMethods
|
114
|
+
Model::HOOKS.each{|h| class_eval("def #{h}; return false if super == false; run_hooks(:#{h}); end", __FILE__, __LINE__)}
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# Runs all hook blocks of given hook type on this object.
|
119
|
+
# Stops running hook blocks and returns false if any hook block returns false.
|
120
|
+
def run_hooks(hook)
|
121
|
+
model.hook_blocks(hook){|block| return false if instance_eval(&block) == false}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The identity_map plugin allows the user to create temporary identity maps
|
4
|
+
# via the with_identity_map method, which takes a block. Inside the block,
|
5
|
+
# objects have a 1-1 correspondence with rows in the database.
|
6
|
+
#
|
7
|
+
# For example, the following is true, and wouldn't be true if you weren't
|
8
|
+
# using the identity map:
|
9
|
+
# Sequel::Model.with_identity_map do
|
10
|
+
# Album.filter{(id > 0) & (id < 2)}.first.object_id == Album.first(:id=>1).object_id
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# In additional to providing a 1-1 correspondence, the identity_map plugin
|
14
|
+
# also provides a cached looked up of records in two cases:
|
15
|
+
# * Model.[] (e.g. Album[1])
|
16
|
+
# * Model.many_to_one accessor methods (e.g. album.artist)
|
17
|
+
#
|
18
|
+
# If the object you are looking up using one of those two methods is already
|
19
|
+
# in the identity map, the record is returned without a database query being
|
20
|
+
# issued.
|
21
|
+
#
|
22
|
+
# Identity maps are thread-local and only presist for the duration of the block,
|
23
|
+
# so they should be should only be considered as a possible performance enhancer.
|
24
|
+
module IdentityMap
|
25
|
+
module ClassMethods
|
26
|
+
# Returns the current thread-local identity map. Should be a hash if
|
27
|
+
# there is an active identity map, and nil otherwise.
|
28
|
+
def identity_map
|
29
|
+
Thread.current[:sequel_identity_map]
|
30
|
+
end
|
31
|
+
|
32
|
+
# The identity map key for an object of the current class with the given pk.
|
33
|
+
# May not always be correct for a class which uses STI.
|
34
|
+
def identity_map_key(pk)
|
35
|
+
"#{self}:#{pk ? Array(pk).join(',') : "nil:#{rand}"}"
|
36
|
+
end
|
37
|
+
|
38
|
+
# If the identity map is in use, check it for a current copy of the object.
|
39
|
+
# If a copy does not exist, create a new object and add it to the identity map.
|
40
|
+
# If a copy exists, add any values in the given row that aren't currently
|
41
|
+
# in the object to the object's values. This allows you to only request
|
42
|
+
# certain fields in an initial query, make modifications to some of those
|
43
|
+
# fields and request other, potentially overlapping fields in a new query,
|
44
|
+
# and not have the second query override fields you modified.
|
45
|
+
def load(row)
|
46
|
+
return super unless idm = identity_map
|
47
|
+
if o = idm[identity_map_key(Array(primary_key).map{|x| row[x]})]
|
48
|
+
o.merge_db_update(row)
|
49
|
+
else
|
50
|
+
o = super
|
51
|
+
idm[identity_map_key(o.pk)] = o
|
52
|
+
end
|
53
|
+
o
|
54
|
+
end
|
55
|
+
|
56
|
+
# Take a block and inside that block use an identity map to ensure a 1-1
|
57
|
+
# correspondence of objects to the database row they represent.
|
58
|
+
def with_identity_map
|
59
|
+
return yield if identity_map
|
60
|
+
begin
|
61
|
+
self.identity_map = {}
|
62
|
+
yield
|
63
|
+
ensure
|
64
|
+
self.identity_map = nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Set the thread local identity map to the given value.
|
71
|
+
def identity_map=(v)
|
72
|
+
Thread.current[:sequel_identity_map] = v
|
73
|
+
end
|
74
|
+
|
75
|
+
# Check the current identity map if it exists for the object with
|
76
|
+
# the matching pk. If one is found, return it, otherwise call super.
|
77
|
+
def primary_key_lookup(pk)
|
78
|
+
(idm = identity_map and o = idm[identity_map_key(pk)]) ? o : super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module InstanceMethods
|
83
|
+
# Remove instances from the identity map cache if they are deleted.
|
84
|
+
def delete
|
85
|
+
super
|
86
|
+
if idm = model.identity_map
|
87
|
+
idm.delete(model.identity_map_key(pk))
|
88
|
+
end
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Merge the current values into the values provided in the row, ensuring
|
93
|
+
# that current values are not overridden by new values.
|
94
|
+
def merge_db_update(row)
|
95
|
+
@values = row.merge(@values)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# If the association is a many_to_one and it has a :key option and the
|
101
|
+
# key option has a value and the association uses the primary key of
|
102
|
+
# the associated class as the :primary_key option, check the identity
|
103
|
+
# map for the associated object and return it if present.
|
104
|
+
def _load_associated_objects(opts)
|
105
|
+
klass = opts.associated_class
|
106
|
+
if idm = model.identity_map and opts[:type] == :many_to_one and opts[:primary_key] == klass.primary_key and
|
107
|
+
opts[:key] and pk = send(opts[:key]) and o = idm[klass.identity_map_key(pk)]
|
108
|
+
o
|
109
|
+
else
|
110
|
+
super
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|