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,41 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# Sequel by default does not use proxies for associations. The association
|
4
|
+
# method for *_to_many associations returns an array, and the association_dataset
|
5
|
+
# method returns a dataset. This plugin makes the association method return a proxy
|
6
|
+
# that will load the association and call a method on the association array if sent
|
7
|
+
# an array method, and otherwise send the method to the association's dataset.
|
8
|
+
module AssociationProxies
|
9
|
+
# A proxy for the association. Calling an array method will load the
|
10
|
+
# associated objects and call the method on the associated object array.
|
11
|
+
# Calling any other method will call that method on the association's dataset.
|
12
|
+
class AssociationProxy < BasicObject
|
13
|
+
# Empty array used to check if an array responds to the given method.
|
14
|
+
ARRAY = []
|
15
|
+
|
16
|
+
# Set the association reflection to use, and whether the association should be
|
17
|
+
# reloaded if an array method is called.
|
18
|
+
def initialize(instance, reflection, reload=nil)
|
19
|
+
@instance = instance
|
20
|
+
@reflection = reflection
|
21
|
+
@reload = reload
|
22
|
+
end
|
23
|
+
|
24
|
+
# Call the method given on the array of associated objects if the method
|
25
|
+
# is an array method, otherwise call the method on the association's dataset.
|
26
|
+
def method_missing(meth, *args, &block)
|
27
|
+
(ARRAY.respond_to?(meth) ? @instance.send(:load_associated_objects, @reflection, @reload) : @instance.send(@reflection.dataset_method)).
|
28
|
+
send(meth, *args, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
# Changes the association method to return a proxy instead of the associated objects
|
34
|
+
# directly.
|
35
|
+
def def_association_method(opts)
|
36
|
+
opts.returns_array? ? association_module_def(opts.association_method){|*r| AssociationProxy.new(self, opts, r[0])} : super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The BooleaReaders plugin allows for the creation of attribute? methods
|
4
|
+
# for boolean columns, which provide a nicer API. By default, the accessors
|
5
|
+
# are created for all columns of type :boolean. However, you can provide a
|
6
|
+
# block to the plugin to change the criteria used to determine if a
|
7
|
+
# column is boolean:
|
8
|
+
#
|
9
|
+
# Sequel::Model.plugin(:boolean_readers){|c| db_schema[c][:db_type] =~ /\Atinyint/}
|
10
|
+
#
|
11
|
+
# This may be useful if you are using MySQL and have some tinyint columns
|
12
|
+
# that represent booleans and others that represent integers. You can turn
|
13
|
+
# the convert_tinyint_to_bool setting off and use the attribute methods for
|
14
|
+
# the integer value and the attribute? methods for the boolean value.
|
15
|
+
module BooleanReaders
|
16
|
+
# Default proc for determining if given column is a boolean, which
|
17
|
+
# just checks that the :type is boolean.
|
18
|
+
DEFAULT_BOOLEAN_ATTRIBUTE_PROC = lambda{|c| s = db_schema[c] and s[:type] == :boolean}
|
19
|
+
|
20
|
+
# Add the boolean_attribute? class method to the model, and create
|
21
|
+
# attribute? boolean reader methods for the class's columns if the class has a dataset.
|
22
|
+
def self.configure(model, &block)
|
23
|
+
model.meta_def(:boolean_attribute?, &(block || DEFAULT_BOOLEAN_ATTRIBUTE_PROC))
|
24
|
+
model.instance_eval{send(:create_boolean_readers) if @dataset}
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
# Create boolean readers for the class using the columns from the new dataset.
|
29
|
+
def set_dataset(*args)
|
30
|
+
super
|
31
|
+
create_boolean_readers
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Add a attribute? method for the column to a module included in the class.
|
38
|
+
def create_boolean_reader(column)
|
39
|
+
overridable_methods_module.module_eval do
|
40
|
+
define_method("#{column}?"){model.db.typecast_value(:boolean, send(column))}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add attribute? methods for all of the boolean attributes for this model.
|
45
|
+
def create_boolean_readers
|
46
|
+
im = instance_methods.collect{|x| x.to_s}
|
47
|
+
cs = columns rescue return
|
48
|
+
cs.each{|c| create_boolean_reader(c) if boolean_attribute?(c) && !im.include?("#{c}?")}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,141 @@
|
|
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 (or memcached API with the :ignore_exceptions
|
5
|
+
# option). You can add caching for any model 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
|
+
# cache_store.delete(key) # Remove key from cache
|
19
|
+
#
|
20
|
+
# If the :ignore_exceptions option is true, exceptions raised by cache_store.get
|
21
|
+
# are ignored and nil is returned instead. The memcached API is to
|
22
|
+
# raise an exception for a missing record, so if you use memcached, you will
|
23
|
+
# want to use this option.
|
24
|
+
#
|
25
|
+
# Note that only Model.[] method calls with a primary key argument are cached
|
26
|
+
# using this plugin.
|
27
|
+
module Caching
|
28
|
+
# Set the cache_store and cache_ttl attributes for the given model.
|
29
|
+
# If the :ttl option is not given, 3600 seconds is the default.
|
30
|
+
def self.configure(model, store, opts={})
|
31
|
+
model.instance_eval do
|
32
|
+
@cache_store = store
|
33
|
+
@cache_ttl = opts[:ttl] || 3600
|
34
|
+
@cache_ignore_exceptions = opts[:ignore_exceptions]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
# If true, ignores exceptions when gettings cached records (the memcached API).
|
40
|
+
attr_reader :cache_ignore_exceptions
|
41
|
+
|
42
|
+
# The cache store object for the model, which should implement the
|
43
|
+
# Ruby-Memcache (or memcached) API
|
44
|
+
attr_reader :cache_store
|
45
|
+
|
46
|
+
# The time to live for the cache store, in seconds.
|
47
|
+
attr_reader :cache_ttl
|
48
|
+
|
49
|
+
# Set the time to live for the cache store, in seconds (default is 3600, # so 1 hour).
|
50
|
+
def set_cache_ttl(ttl)
|
51
|
+
@cache_ttl = ttl
|
52
|
+
end
|
53
|
+
|
54
|
+
# Copy the necessary class instance variables to the subclass.
|
55
|
+
def inherited(subclass)
|
56
|
+
super
|
57
|
+
store = @cache_store
|
58
|
+
ttl = @cache_ttl
|
59
|
+
cache_ignore_exceptions = @cache_ignore_exceptions
|
60
|
+
subclass.instance_eval do
|
61
|
+
@cache_store = store
|
62
|
+
@cache_ttl = ttl
|
63
|
+
@cache_ignore_exceptions = cache_ignore_exceptions
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Delete the entry with the matching key from the cache
|
70
|
+
def cache_delete(ck)
|
71
|
+
@cache_store.delete(ck)
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def cache_get(ck)
|
76
|
+
if @cache_ignore_exceptions
|
77
|
+
@cache_store.get(ck) rescue nil
|
78
|
+
else
|
79
|
+
@cache_store.get(ck)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return a key string for the pk
|
84
|
+
def cache_key(pk)
|
85
|
+
"#{self}:#{Array(pk).join(',')}"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Set the object in the cache_store with the given key for cache_ttl seconds.
|
89
|
+
def cache_set(ck, obj)
|
90
|
+
@cache_store.set(ck, obj, @cache_ttl)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Check the cache before a database lookup unless a hash is supplied.
|
94
|
+
def primary_key_lookup(pk)
|
95
|
+
ck = cache_key(pk)
|
96
|
+
unless obj = cache_get(ck)
|
97
|
+
if obj = super(pk)
|
98
|
+
cache_set(ck, obj)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
obj
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
module InstanceMethods
|
106
|
+
# Remove the object from the cache when updating
|
107
|
+
def before_update
|
108
|
+
return false if super == false
|
109
|
+
cache_delete
|
110
|
+
end
|
111
|
+
|
112
|
+
# Return a key unique to the underlying record for caching, based on the
|
113
|
+
# primary key value(s) for the object. If the model does not have a primary
|
114
|
+
# key, raise an Error.
|
115
|
+
def cache_key
|
116
|
+
raise(Error, "No primary key is associated with this model") unless key = primary_key
|
117
|
+
pk = case key
|
118
|
+
when Array
|
119
|
+
key.collect{|k| @values[k]}
|
120
|
+
else
|
121
|
+
@values[key] || (raise Error, 'no primary key for this record')
|
122
|
+
end
|
123
|
+
model.send(:cache_key, pk)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Remove the object from the cache when deleting
|
127
|
+
def delete
|
128
|
+
cache_delete
|
129
|
+
super
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
# Delete this object from the cache
|
135
|
+
def cache_delete
|
136
|
+
model.send(:cache_delete, cache_key)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The class_table_inheritance plugin allows you to model inheritance in the
|
4
|
+
# database using a table per model class in the hierarchy, with only columns
|
5
|
+
# unique to that model class (or subclass hierarchy) being stored in the related
|
6
|
+
# table. For example, with this hierarchy:
|
7
|
+
#
|
8
|
+
# Employee
|
9
|
+
# / \
|
10
|
+
# Staff Manager
|
11
|
+
# |
|
12
|
+
# Executive
|
13
|
+
#
|
14
|
+
# the following database schema may be used (table - columns):
|
15
|
+
#
|
16
|
+
# * employees - id, name, kind
|
17
|
+
# * staff - id, manager_id
|
18
|
+
# * managers - id, num_staff
|
19
|
+
# * executives - id, num_managers
|
20
|
+
#
|
21
|
+
# The class_table_inheritance plugin assumes that the main table
|
22
|
+
# (e.g. employees) has a primary key field (usually autoincrementing),
|
23
|
+
# and all other tables have a foreign key of the same name that points
|
24
|
+
# to the same key in their superclass's table. For example:
|
25
|
+
#
|
26
|
+
# * employees.id - primary key, autoincrementing
|
27
|
+
# * staff.id - foreign key referencing employees(id)
|
28
|
+
# * managers.id - foreign key referencing employees(id)
|
29
|
+
# * executives.id - foreign key referencing managers(id)
|
30
|
+
#
|
31
|
+
# When using the class_table_inheritance plugin, subclasses use joined
|
32
|
+
# datasets:
|
33
|
+
#
|
34
|
+
# Employee.dataset.sql # SELECT * FROM employees
|
35
|
+
# Manager.dataset.sql # SELECT * FROM employees
|
36
|
+
# # INNER JOIN managers USING (id)
|
37
|
+
# Executive.dataset.sql # SELECT * FROM employees
|
38
|
+
# # INNER JOIN managers USING (id)
|
39
|
+
# # INNER JOIN executives USING (id)
|
40
|
+
#
|
41
|
+
# This allows Executive.all to return instances with all attributes
|
42
|
+
# loaded. The plugin overrides the deleting, inserting, and updating
|
43
|
+
# in the model to work with multiple tables, by handling each table
|
44
|
+
# individually.
|
45
|
+
#
|
46
|
+
# This plugin allows the use of a :key option when loading to mark
|
47
|
+
# a column holding a class name. This allows methods on the
|
48
|
+
# superclass to return instances of specific subclasses.
|
49
|
+
# This plugin also requires the lazy_attributes plugin and uses it to
|
50
|
+
# return subclass specific attributes that would not be loaded
|
51
|
+
# when calling superclass methods (since those wouldn't join
|
52
|
+
# to the subclass tables). For example:
|
53
|
+
#
|
54
|
+
# a = Employee.all # [<#Staff>, <#Manager>, <#Executive>]
|
55
|
+
# a.first.values # {:id=>1, name=>'S', :kind=>'Staff'}
|
56
|
+
# a.first.manager_id # Loads the manager_id attribute from the database
|
57
|
+
module ClassTableInheritance
|
58
|
+
# The class_table_inheritance plugin requires the lazy_attributes plugin
|
59
|
+
# to handle lazily-loaded attributes for subclass instances returned
|
60
|
+
# by superclass methods.
|
61
|
+
def self.apply(model, opts={}, &block)
|
62
|
+
model.plugin :lazy_attributes
|
63
|
+
end
|
64
|
+
|
65
|
+
# Initialize the per-model data structures and set the dataset's row_proc
|
66
|
+
# to check for the :key option column for the type of class when loading objects.
|
67
|
+
# Options:
|
68
|
+
# * :key - The column symbol holding the name of the model class this
|
69
|
+
# is an instance of. Necessary if you want to call model methods
|
70
|
+
# using the superclass, but have them return subclass instances.
|
71
|
+
# * :table_map - Hash with class name symbol keys and table name symbol
|
72
|
+
# values. Necessary if the implicit table name for the model class
|
73
|
+
# does not match the database table name
|
74
|
+
# Example:
|
75
|
+
# class Employee < Sequel::Model
|
76
|
+
# plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
|
77
|
+
# end
|
78
|
+
def self.configure(model, opts={}, &block)
|
79
|
+
model.instance_eval do
|
80
|
+
m = method(:constantize)
|
81
|
+
@cti_base_model = self
|
82
|
+
@cti_key = key = opts[:key]
|
83
|
+
@cti_tables = [table_name]
|
84
|
+
@cti_columns = {table_name=>columns}
|
85
|
+
@cti_table_map = opts[:table_map] || {}
|
86
|
+
dataset.row_proc = lambda{|r| (m.call(r[key]) rescue model).load(r)}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module ClassMethods
|
91
|
+
# The parent/root/base model for this class table inheritance hierarchy.
|
92
|
+
# This is the only model in the hierarchy that load the
|
93
|
+
# class_table_inheritance plugin.
|
94
|
+
attr_reader :cti_base_model
|
95
|
+
|
96
|
+
# Hash with table name symbol keys and arrays of column symbol values,
|
97
|
+
# giving the columns to update in each backing database table.
|
98
|
+
attr_reader :cti_columns
|
99
|
+
|
100
|
+
# The column containing the class name as a string. Used to
|
101
|
+
# return instances of subclasses when calling the superclass's
|
102
|
+
# load method.
|
103
|
+
attr_reader :cti_key
|
104
|
+
|
105
|
+
# An array of table symbols that back this model. The first is
|
106
|
+
# cti_base_model table symbol, and the last is the current model
|
107
|
+
# table symbol.
|
108
|
+
attr_reader :cti_tables
|
109
|
+
|
110
|
+
# A hash with class name symbol keys and table name symbol values.
|
111
|
+
# Specified with the :table_map option to the plugin, and used if
|
112
|
+
# the implicit naming is incorrect.
|
113
|
+
attr_reader :cti_table_map
|
114
|
+
|
115
|
+
# Add the appropriate data structures to the subclass. Does not
|
116
|
+
# allow anonymous subclasses to be created, since they would not
|
117
|
+
# be mappable to a table.
|
118
|
+
def inherited(subclass)
|
119
|
+
cc = cti_columns
|
120
|
+
ck = cti_key
|
121
|
+
ct = cti_tables.dup
|
122
|
+
ctm = cti_table_map.dup
|
123
|
+
cbm = cti_base_model
|
124
|
+
pk = primary_key
|
125
|
+
ds = dataset
|
126
|
+
subclass.instance_eval do
|
127
|
+
raise(Error, "cannot create anonymous subclass for model class using class_table_inheritance") if !(n = name) || n.empty?
|
128
|
+
table = ctm[n.to_sym] || implicit_table_name
|
129
|
+
columns = db.from(table).columns
|
130
|
+
@cti_key = ck
|
131
|
+
@cti_tables = ct + [table]
|
132
|
+
@cti_columns = cc.merge(table=>columns)
|
133
|
+
@cti_table_map = ctm
|
134
|
+
@cti_base_model = cbm
|
135
|
+
# Need to set dataset and columns before calling super so that
|
136
|
+
# the main column accessor module is included in the class before any
|
137
|
+
# plugin accessor modules (such as the lazy attributes accessor module).
|
138
|
+
set_dataset(ds.join(table, [pk]))
|
139
|
+
set_columns(self.columns)
|
140
|
+
end
|
141
|
+
super
|
142
|
+
subclass.instance_eval do
|
143
|
+
m = method(:constantize)
|
144
|
+
dataset.row_proc = lambda{|r| (m.call(r[ck]) rescue subclass).load(r)}
|
145
|
+
(columns - [cbm.primary_key]).each{|a| define_lazy_attribute_getter(a)}
|
146
|
+
cti_tables.reverse.each do |table|
|
147
|
+
db.schema(table).each{|k,v| db_schema[k] = v}
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# The primary key in the parent/base/root model, which should have a
|
153
|
+
# foreign key with the same name referencing it in each model subclass.
|
154
|
+
def primary_key
|
155
|
+
return super if self == cti_base_model
|
156
|
+
cti_base_model.primary_key
|
157
|
+
end
|
158
|
+
|
159
|
+
# The table name for the current model class's main table (not used
|
160
|
+
# by any superclasses).
|
161
|
+
def table_name
|
162
|
+
self == cti_base_model ? super : cti_tables.last
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
module InstanceMethods
|
167
|
+
# Set the cti_key column to the name of the model.
|
168
|
+
def before_create
|
169
|
+
return false if super == false
|
170
|
+
send("#{model.cti_key}=", model.name.to_s) if model.cti_key
|
171
|
+
end
|
172
|
+
|
173
|
+
# Delete the row from all backing tables, starting from the
|
174
|
+
# most recent table and going through all superclasses.
|
175
|
+
def delete
|
176
|
+
m = model
|
177
|
+
m.cti_tables.reverse.each do |table|
|
178
|
+
m.db.from(table).filter(m.primary_key=>pk).delete
|
179
|
+
end
|
180
|
+
self
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
# Insert rows into all backing tables, using the columns
|
186
|
+
# in each table.
|
187
|
+
def _insert
|
188
|
+
return super if model == model.cti_base_model
|
189
|
+
iid = nil
|
190
|
+
m = model
|
191
|
+
m.cti_tables.each do |table|
|
192
|
+
h = {}
|
193
|
+
h[m.primary_key] = iid if iid
|
194
|
+
m.cti_columns[table].each{|c| h[c] = @values[c] if @values.include?(c)}
|
195
|
+
nid = m.db.from(table).insert(h)
|
196
|
+
iid ||= nid
|
197
|
+
end
|
198
|
+
@values[primary_key] = iid
|
199
|
+
end
|
200
|
+
|
201
|
+
# Update rows in all backing tables, using the columns in each table.
|
202
|
+
def _update(columns)
|
203
|
+
pkh = pk_hash
|
204
|
+
m = model
|
205
|
+
m.cti_tables.each do |table|
|
206
|
+
h = {}
|
207
|
+
m.cti_columns[table].each{|c| h[c] = columns[c] if columns.include?(c)}
|
208
|
+
m.db.from(table).filter(pkh).update(h) unless h.empty?
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|