viking-sequel 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|