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,81 @@
|
|
1
|
+
module Sequel
|
2
|
+
require 'plugins/instance_filters'
|
3
|
+
|
4
|
+
module Plugins
|
5
|
+
# This plugin implements a simple database-independent locking mechanism
|
6
|
+
# to ensure that concurrent updates do not override changes. This is
|
7
|
+
# best implemented by a code example:
|
8
|
+
#
|
9
|
+
# class Person < Sequel::Model
|
10
|
+
# plugin :optimistic_locking
|
11
|
+
# end
|
12
|
+
# p1 = Person[1]
|
13
|
+
# p2 = Person[1]
|
14
|
+
# p1.update(:name=>'Jim') # works
|
15
|
+
# p2.update(:name=>'Bob') # raises Sequel::Plugins::OptimisticLocking::Error
|
16
|
+
#
|
17
|
+
# In order for this plugin to work, you need to make sure that the database
|
18
|
+
# table has a lock_version column (or other column you name via the lock_column
|
19
|
+
# class level accessor) that defaults to 0.
|
20
|
+
#
|
21
|
+
# This plugin relies on the instance_filters plugin.
|
22
|
+
module OptimisticLocking
|
23
|
+
# Exception class raised when trying to update or destroy a stale object.
|
24
|
+
Error = InstanceFilters::Error
|
25
|
+
|
26
|
+
# Load the instance_filters plugin into the model.
|
27
|
+
def self.apply(model, opts={})
|
28
|
+
model.plugin :instance_filters
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set the lock_column to the :lock_column option, or :lock_version if
|
32
|
+
# that option is not given.
|
33
|
+
def self.configure(model, opts={})
|
34
|
+
model.lock_column = opts[:lock_column] || :lock_version
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
# The column holding the version of the lock
|
39
|
+
attr_accessor :lock_column
|
40
|
+
|
41
|
+
# Copy the lock_column value into the subclass
|
42
|
+
def inherited(subclass)
|
43
|
+
super
|
44
|
+
subclass.lock_column = lock_column
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module InstanceMethods
|
49
|
+
# Add the lock column instance filter to the object before destroying it.
|
50
|
+
def before_destroy
|
51
|
+
lock_column_instance_filter
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add the lock column instance filter to the object before updating it.
|
56
|
+
def before_update
|
57
|
+
lock_column_instance_filter
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Add the lock column instance filter to the object.
|
64
|
+
def lock_column_instance_filter
|
65
|
+
lc = model.lock_column
|
66
|
+
instance_filter(lc=>send(lc))
|
67
|
+
end
|
68
|
+
|
69
|
+
# Only update the row if it has the same lock version, and increment the
|
70
|
+
# lock version.
|
71
|
+
def _update(columns)
|
72
|
+
lc = model.lock_column
|
73
|
+
lcv = send(lc)
|
74
|
+
columns[lc] = lcv + 1
|
75
|
+
super
|
76
|
+
send("#{lc}=", lcv + 1)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# = Overview
|
4
|
+
#
|
5
|
+
# The rcte_tree plugin deals with tree structured data stored
|
6
|
+
# in the database using the adjacency list model (where child rows
|
7
|
+
# have a foreign key pointing to the parent rows), using recursive
|
8
|
+
# common table expressions to load all ancestors in a single query,
|
9
|
+
# all descendants in a single query, and all descendants to a given
|
10
|
+
# level (where level 1 is children, level 2 is children and grandchildren
|
11
|
+
# etc.) in a single query.
|
12
|
+
#
|
13
|
+
# = Background
|
14
|
+
#
|
15
|
+
# There are two types of common models for storing tree structured data
|
16
|
+
# in an SQL database, the adjacency list model and the nested set model.
|
17
|
+
# Before recursive common table expressions (or similar capabilities such
|
18
|
+
# as CONNECT BY for Oracle), the nested set model was the only easy way
|
19
|
+
# to retrieve all ancestors and descendants in a single query. However,
|
20
|
+
# it has significant performance corner cases.
|
21
|
+
#
|
22
|
+
# On PostgreSQL 8.4, with a significant number of rows, the nested set
|
23
|
+
# model is almost 500 times slower than using a recursive common table
|
24
|
+
# expression with the adjacency list model to get all descendants, and
|
25
|
+
# almost 24,000 times slower to get all descendants to a given level.
|
26
|
+
#
|
27
|
+
# Considering that the nested set model requires more difficult management
|
28
|
+
# than the adjacency list model, it's almost always better to use the
|
29
|
+
# adjacency list model if your database supports common table expressions.
|
30
|
+
# See http://explainextended.com/2009/09/24/adjacency-list-vs-nested-sets-postgresql/
|
31
|
+
# for detailed analysis.
|
32
|
+
#
|
33
|
+
# = Usage
|
34
|
+
#
|
35
|
+
# The rcte_tree plugin is unlike most plugins in that it doesn't add any class,
|
36
|
+
# instance, or dataset modules. It only has a single apply method, which
|
37
|
+
# adds four associations to the model: parent, children, ancestors, and
|
38
|
+
# descendants. Both the parent and children are fairly standard many_to_one
|
39
|
+
# and one_to_many associations, respectively. However, the ancestors and
|
40
|
+
# descendants associations are special. Both the ancestors and descendants
|
41
|
+
# associations will automatically set the parent and children associations,
|
42
|
+
# respectively, for current object and all of the ancestor or descendant
|
43
|
+
# objects, whenever they are loaded (either eagerly or lazily). Additionally,
|
44
|
+
# the descendants association can take a level argument when called eagerly,
|
45
|
+
# which limits the returned objects to only that many levels in the tree (see
|
46
|
+
# the Overview).
|
47
|
+
#
|
48
|
+
# Model.plugin :rcte_tree
|
49
|
+
#
|
50
|
+
# # Lazy loading
|
51
|
+
# model = Model.first
|
52
|
+
# model.parent
|
53
|
+
# model.children
|
54
|
+
# model.ancestors # Populates :parent association for all ancestors
|
55
|
+
# model.descendants # Populates :children association for all descendants
|
56
|
+
#
|
57
|
+
# # Eager loading - also populates the :parent and children associations
|
58
|
+
# # for all ancestors and descendants
|
59
|
+
# Model.filter(:id=>[1, 2]).eager(:ancestors, :descendants).all
|
60
|
+
#
|
61
|
+
# # Eager loading children and grand children
|
62
|
+
# Model.filter(:id=>[1, 2]).eager(:descendants=>2).all
|
63
|
+
# # Eager loading children, grand children, and great grand children
|
64
|
+
# Model.filter(:id=>[1, 2]).eager(:descendants=>3).all
|
65
|
+
#
|
66
|
+
# = Options
|
67
|
+
#
|
68
|
+
# You can override the options for any specific association by making
|
69
|
+
# sure the plugin options contain one of the following keys:
|
70
|
+
#
|
71
|
+
# * :parent - hash of options for the parent association
|
72
|
+
# * :children - hash of options for the children association
|
73
|
+
# * :ancestors - hash of options for the ancestors association
|
74
|
+
# * :descendants - hash of options for the descendants association
|
75
|
+
#
|
76
|
+
# Note that you can change the name of the above associations by specifying
|
77
|
+
# a :name key in the appropriate hash of options above. For example:
|
78
|
+
#
|
79
|
+
# Model.plugin :rcte_tree, :parent=>{:name=>:mother},
|
80
|
+
# :children=>{:name=>:daughters}, :descendants=>{:name=>:offspring}
|
81
|
+
#
|
82
|
+
# Any other keys in the main options hash are treated as options shared by
|
83
|
+
# all of the associations. Here's a few options that affect the plugin:
|
84
|
+
#
|
85
|
+
# * :key - The foreign key in the table that points to the primary key
|
86
|
+
# of the parent (default: :parent_id)
|
87
|
+
# * :primary_key - The primary key to use (default: the model's primary key)
|
88
|
+
# * :key_alias - The symbol identifier to use for aliasing when eager
|
89
|
+
# loading (default: :x_root_x)
|
90
|
+
# * :cte_name - The symbol identifier to use for the common table expression
|
91
|
+
# (default: :t)
|
92
|
+
# * :level_alias - The symbol identifier to use when eagerly loading descendants
|
93
|
+
# up to a given level (default: :x_level_x)
|
94
|
+
module RcteTree
|
95
|
+
# Create the appropriate parent, children, ancestors, and descendants
|
96
|
+
# associations for the model.
|
97
|
+
def self.apply(model, opts={})
|
98
|
+
opts = opts.dup
|
99
|
+
opts[:class] = model
|
100
|
+
|
101
|
+
key = opts[:key] ||= :parent_id
|
102
|
+
prkey = opts[:primary_key] ||= model.primary_key
|
103
|
+
|
104
|
+
par = opts.merge(opts.fetch(:parent, {}))
|
105
|
+
parent = par.fetch(:name, :parent)
|
106
|
+
model.many_to_one parent, par
|
107
|
+
|
108
|
+
chi = opts.merge(opts.fetch(:children, {}))
|
109
|
+
childrena = chi.fetch(:name, :children)
|
110
|
+
model.one_to_many childrena, chi
|
111
|
+
|
112
|
+
ka = opts[:key_alias] ||= :x_root_x
|
113
|
+
t = opts[:cte_name] ||= :t
|
114
|
+
opts[:reciprocal] = nil
|
115
|
+
c_all = SQL::ColumnAll.new(model.table_name)
|
116
|
+
|
117
|
+
a = opts.merge(opts.fetch(:ancestors, {}))
|
118
|
+
ancestors = a.fetch(:name, :ancestors)
|
119
|
+
a[:read_only] = true unless a.has_key?(:read_only)
|
120
|
+
a[:eager_loader_key] = key
|
121
|
+
a[:dataset] ||= proc do
|
122
|
+
model.from(t).
|
123
|
+
with_recursive(t, model.filter(prkey=>send(key)),
|
124
|
+
model.join(t, key=>prkey).
|
125
|
+
select(c_all))
|
126
|
+
end
|
127
|
+
aal = Array(a[:after_load])
|
128
|
+
aal << proc do |m, ancs|
|
129
|
+
unless m.associations.has_key?(parent)
|
130
|
+
parent_map = {m[prkey]=>m}
|
131
|
+
child_map = {}
|
132
|
+
child_map[m[key]] = m if m[key]
|
133
|
+
m.associations[parent] = nil
|
134
|
+
ancs.each do |obj|
|
135
|
+
obj.associations[parent] = nil
|
136
|
+
parent_map[obj[prkey]] = obj
|
137
|
+
if ok = obj[key]
|
138
|
+
child_map[ok] = obj
|
139
|
+
end
|
140
|
+
end
|
141
|
+
parent_map.each do |parent_id, obj|
|
142
|
+
if child = child_map[parent_id]
|
143
|
+
child.associations[parent] = obj
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
a[:after_load] ||= aal
|
149
|
+
a[:eager_loader] ||= proc do |key_hash, objects, associations|
|
150
|
+
id_map = key_hash[key]
|
151
|
+
parent_map = {}
|
152
|
+
children_map = {}
|
153
|
+
objects.each do |obj|
|
154
|
+
parent_map[obj[prkey]] = obj
|
155
|
+
(children_map[obj[key]] ||= []) << obj
|
156
|
+
obj.associations[ancestors] = []
|
157
|
+
obj.associations[parent] = nil
|
158
|
+
end
|
159
|
+
r = model.association_reflection(ancestors)
|
160
|
+
model.eager_loading_dataset(r,
|
161
|
+
model.from(t).
|
162
|
+
with_recursive(t, model.filter(prkey=>id_map.keys).
|
163
|
+
select(SQL::AliasedExpression.new(prkey, ka), c_all),
|
164
|
+
model.join(t, key=>prkey).
|
165
|
+
select(SQL::QualifiedIdentifier.new(t, ka), c_all)),
|
166
|
+
r.select,
|
167
|
+
associations).all do |obj|
|
168
|
+
opk = obj[prkey]
|
169
|
+
if in_pm = parent_map.has_key?(opk)
|
170
|
+
if idm_obj = parent_map[opk]
|
171
|
+
idm_obj.values[ka] = obj.values[ka]
|
172
|
+
obj = idm_obj
|
173
|
+
end
|
174
|
+
else
|
175
|
+
obj.associations[parent] = nil
|
176
|
+
parent_map[opk] = obj
|
177
|
+
(children_map[obj[key]] ||= []) << obj
|
178
|
+
end
|
179
|
+
|
180
|
+
if roots = id_map[obj.values.delete(ka)]
|
181
|
+
roots.each do |root|
|
182
|
+
root.associations[ancestors] << obj
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
parent_map.each do |parent_id, obj|
|
187
|
+
if children = children_map[parent_id]
|
188
|
+
children.each do |child|
|
189
|
+
child.associations[parent] = obj
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
model.one_to_many ancestors, a
|
195
|
+
|
196
|
+
d = opts.merge(opts.fetch(:descendants, {}))
|
197
|
+
descendants = d.fetch(:name, :descendants)
|
198
|
+
d[:read_only] = true unless d.has_key?(:read_only)
|
199
|
+
la = d[:level_alias] ||= :x_level_x
|
200
|
+
d[:dataset] ||= proc do
|
201
|
+
model.from(t).
|
202
|
+
with_recursive(t, model.filter(key=>send(prkey)),
|
203
|
+
model.join(t, prkey=>key).
|
204
|
+
select(SQL::ColumnAll.new(model.table_name)))
|
205
|
+
end
|
206
|
+
dal = Array(d[:after_load])
|
207
|
+
dal << proc do |m, descs|
|
208
|
+
unless m.associations.has_key?(childrena)
|
209
|
+
parent_map = {m[prkey]=>m}
|
210
|
+
children_map = {}
|
211
|
+
m.associations[childrena] = []
|
212
|
+
descs.each do |obj|
|
213
|
+
obj.associations[childrena] = []
|
214
|
+
if opk = obj[prkey]
|
215
|
+
parent_map[opk] = obj
|
216
|
+
end
|
217
|
+
if ok = obj[key]
|
218
|
+
(children_map[ok] ||= []) << obj
|
219
|
+
end
|
220
|
+
end
|
221
|
+
children_map.each do |parent_id, objs|
|
222
|
+
parent_map[parent_id].associations[childrena] = objs
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
d[:after_load] = dal
|
227
|
+
d[:eager_loader] ||= proc do |key_hash, objects, associations|
|
228
|
+
id_map = key_hash[prkey]
|
229
|
+
parent_map = {}
|
230
|
+
children_map = {}
|
231
|
+
objects.each do |obj|
|
232
|
+
parent_map[obj[prkey]] = obj
|
233
|
+
obj.associations[descendants] = []
|
234
|
+
obj.associations[childrena] = []
|
235
|
+
end
|
236
|
+
r = model.association_reflection(descendants)
|
237
|
+
base_case = model.filter(key=>id_map.keys).
|
238
|
+
select(SQL::AliasedExpression.new(key, ka), c_all)
|
239
|
+
recursive_case = model.join(t, prkey=>key).
|
240
|
+
select(SQL::QualifiedIdentifier.new(t, ka), c_all)
|
241
|
+
if associations.is_a?(Integer)
|
242
|
+
level = associations
|
243
|
+
no_cache_level = level - 1
|
244
|
+
associations = {}
|
245
|
+
base_case = base_case.select_more(SQL::AliasedExpression.new(0, la))
|
246
|
+
recursive_case = recursive_case.select_more(SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, la) + 1, la)).filter(SQL::QualifiedIdentifier.new(t, la) < level - 1)
|
247
|
+
end
|
248
|
+
model.eager_loading_dataset(r,
|
249
|
+
model.from(t).with_recursive(t, base_case, recursive_case),
|
250
|
+
r.select,
|
251
|
+
associations).all do |obj|
|
252
|
+
if level
|
253
|
+
no_cache = no_cache_level == obj.values.delete(la)
|
254
|
+
end
|
255
|
+
|
256
|
+
opk = obj[prkey]
|
257
|
+
if in_pm = parent_map.has_key?(opk)
|
258
|
+
if idm_obj = parent_map[opk]
|
259
|
+
idm_obj.values[ka] = obj.values[ka]
|
260
|
+
obj = idm_obj
|
261
|
+
end
|
262
|
+
else
|
263
|
+
obj.associations[childrena] = [] unless no_cache
|
264
|
+
parent_map[opk] = obj
|
265
|
+
end
|
266
|
+
|
267
|
+
if root = id_map[obj.values.delete(ka)].first
|
268
|
+
root.associations[descendants] << obj
|
269
|
+
end
|
270
|
+
|
271
|
+
(children_map[obj[key]] ||= []) << obj
|
272
|
+
end
|
273
|
+
children_map.each do |parent_id, objs|
|
274
|
+
parent_map[parent_id].associations[childrena] = objs.uniq
|
275
|
+
end
|
276
|
+
end
|
277
|
+
model.one_to_many descendants, d
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# Sequel's built in schema plugin allows you to define your schema
|
4
|
+
# directly in the model using Model.set_schema (which takes a block
|
5
|
+
# similar to Database#create_table), and use Model.create_table to
|
6
|
+
# create a table using the schema information.
|
7
|
+
#
|
8
|
+
# This plugin is mostly suited to test code. If there is any
|
9
|
+
# chance that your application's schema could change, you should
|
10
|
+
# be using the migration extension instead.
|
11
|
+
module Schema
|
12
|
+
module ClassMethods
|
13
|
+
# Creates table, using the column information from set_schema.
|
14
|
+
def create_table
|
15
|
+
db.create_table(table_name, :generator=>@schema)
|
16
|
+
@db_schema = get_db_schema(true)
|
17
|
+
columns
|
18
|
+
end
|
19
|
+
|
20
|
+
# Drops the table if it exists and then runs create_table. Should probably
|
21
|
+
# not be used except in testing.
|
22
|
+
def create_table!
|
23
|
+
drop_table rescue nil
|
24
|
+
create_table
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates the table unless the table already exists
|
28
|
+
def create_table?
|
29
|
+
create_table unless table_exists?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Drops table.
|
33
|
+
def drop_table
|
34
|
+
db.drop_table(table_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns table schema created with set_schema for direct descendant of Model.
|
38
|
+
# Does not retreive schema information from the database, see db_schema if you
|
39
|
+
# want that.
|
40
|
+
def schema
|
41
|
+
@schema || (superclass.schema unless superclass == Model)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Defines a table schema (see Schema::Generator for more information).
|
45
|
+
#
|
46
|
+
# This is only needed if you want to use the create_table/create_table! methods.
|
47
|
+
# Will also set the dataset if you provide a name, as well as setting
|
48
|
+
# the primary key if you defined one in the passed block.
|
49
|
+
#
|
50
|
+
# In general, it is a better idea to use migrations for production code, as
|
51
|
+
# migrations allow changes to existing schema. set_schema is mostly useful for
|
52
|
+
# test code or simple examples.
|
53
|
+
def set_schema(name = nil, &block)
|
54
|
+
set_dataset(db[name]) if name
|
55
|
+
@schema = Sequel::Schema::Generator.new(db, &block)
|
56
|
+
set_primary_key(@schema.primary_key_name) if @schema.primary_key_name
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns true if table exists, false otherwise.
|
60
|
+
def table_exists?
|
61
|
+
db.table_exists?(table_name)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# Sequel's built in Serialization plugin allows you to keep serialized
|
4
|
+
# ruby objects in the database, while giving you deserialized objects
|
5
|
+
# when you call an accessor.
|
6
|
+
#
|
7
|
+
# This plugin works by keeping the serialized value in the values, and
|
8
|
+
# adding a @deserialized_values hash. The reader method for serialized columns
|
9
|
+
# will check the @deserialized_values for the value, return it if present,
|
10
|
+
# or deserialized the entry in @values and return it. The writer method will
|
11
|
+
# set the @deserialized_values entry. This plugin adds a before_save hook
|
12
|
+
# that serializes all @deserialized_values to @values.
|
13
|
+
#
|
14
|
+
# You can use either marshal, yaml, or json as the serialization format.
|
15
|
+
# If you use yaml or json, you should require them by yourself.
|
16
|
+
#
|
17
|
+
# Because of how this plugin works, it must be used inside each model class
|
18
|
+
# that needs serialization, after any set_dataset method calls in that class.
|
19
|
+
# Otherwise, it is possible that the default column accessors will take
|
20
|
+
# precedence.
|
21
|
+
#
|
22
|
+
# == Example
|
23
|
+
#
|
24
|
+
# require 'sequel'
|
25
|
+
# require 'json'
|
26
|
+
# class User < Sequel::Model
|
27
|
+
# plugin :serialization, :json, :permissions
|
28
|
+
# # or
|
29
|
+
# plugin :serialization
|
30
|
+
# serialize_attributes :marshal, :permissions, :attributes
|
31
|
+
# end
|
32
|
+
# user = User.create
|
33
|
+
# user.permissions = { :global => 'read-only' }
|
34
|
+
# user.save
|
35
|
+
module Serialization
|
36
|
+
# Set up the column readers to do deserialization and the column writers
|
37
|
+
# to save the value in deserialized_values.
|
38
|
+
def self.apply(model, *args)
|
39
|
+
model.instance_eval{@serialization_map = {}}
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.configure(model, format=nil, *columns)
|
43
|
+
model.serialize_attributes(format, *columns) unless columns.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
# A map of the serialized columns for this model. Keys are column
|
48
|
+
# symbols, values are serialization formats (:marshal, :yaml, or :json).
|
49
|
+
attr_reader :serialization_map
|
50
|
+
|
51
|
+
# Module to store the serialized column accessor methods, so they can
|
52
|
+
# call be overridden and call super to get the serialization behavior
|
53
|
+
attr_accessor :serialization_module
|
54
|
+
|
55
|
+
# Copy the serialization format and columns to serialize into the subclass.
|
56
|
+
def inherited(subclass)
|
57
|
+
super
|
58
|
+
sm = serialization_map.dup
|
59
|
+
subclass.instance_eval{@serialization_map = sm}
|
60
|
+
end
|
61
|
+
|
62
|
+
# The first value in the serialization map. This is only for
|
63
|
+
# backwards compatibility, use serialization_map in new code.
|
64
|
+
def serialization_format
|
65
|
+
serialization_map.values.first
|
66
|
+
end
|
67
|
+
|
68
|
+
# Create instance level reader that deserializes column values on request,
|
69
|
+
# and instance level writer that stores new deserialized value in deserialized
|
70
|
+
# columns
|
71
|
+
def serialize_attributes(format, *columns)
|
72
|
+
raise(Error, "Unsupported serialization format (#{format}), should be :marshal, :yaml, or :json") unless [:marshal, :yaml, :json].include?(format)
|
73
|
+
raise(Error, "No columns given. The serialization plugin requires you specify which columns to serialize") if columns.empty?
|
74
|
+
define_serialized_attribute_accessor(format, *columns)
|
75
|
+
end
|
76
|
+
|
77
|
+
# The columns that will be serialized. This is only for
|
78
|
+
# backwards compatibility, use serialization_map in new code.
|
79
|
+
def serialized_columns
|
80
|
+
serialization_map.keys
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Add serializated attribute acessor methods to the serialization_module
|
86
|
+
def define_serialized_attribute_accessor(format, *columns)
|
87
|
+
m = self
|
88
|
+
include(self.serialization_module ||= Module.new) unless serialization_module
|
89
|
+
serialization_module.class_eval do
|
90
|
+
columns.each do |column|
|
91
|
+
m.serialization_map[column] = format
|
92
|
+
define_method(column) do
|
93
|
+
if deserialized_values.has_key?(column)
|
94
|
+
deserialized_values[column]
|
95
|
+
else
|
96
|
+
deserialized_values[column] = deserialize_value(column, super())
|
97
|
+
end
|
98
|
+
end
|
99
|
+
define_method("#{column}=") do |v|
|
100
|
+
changed_columns << column unless changed_columns.include?(column)
|
101
|
+
deserialized_values[column] = v
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module InstanceMethods
|
109
|
+
# Hash of deserialized values, used as a cache.
|
110
|
+
attr_reader :deserialized_values
|
111
|
+
|
112
|
+
# Set @deserialized_values to the empty hash
|
113
|
+
def initialize(*args, &block)
|
114
|
+
@deserialized_values = {}
|
115
|
+
super
|
116
|
+
end
|
117
|
+
|
118
|
+
# Serialize all deserialized values
|
119
|
+
def before_save
|
120
|
+
super
|
121
|
+
deserialized_values.each do |k,v|
|
122
|
+
@values[k] = serialize_value(k, v)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Empty the deserialized values when refreshing.
|
127
|
+
def refresh
|
128
|
+
@deserialized_values = {}
|
129
|
+
super
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
# Deserialize the column from either marshal or yaml format
|
135
|
+
def deserialize_value(column, v)
|
136
|
+
return v if v.nil?
|
137
|
+
case model.serialization_map[column]
|
138
|
+
when :marshal
|
139
|
+
Marshal.load(v.unpack('m')[0]) rescue Marshal.load(v)
|
140
|
+
when :yaml
|
141
|
+
YAML.load v if v
|
142
|
+
when :json
|
143
|
+
JSON.parse v if v
|
144
|
+
else
|
145
|
+
raise Error, "Bad serialization format (#{model.serialization_map[column].inspect}) for column #{column.inspect}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Serialize the column to either marshal or yaml format
|
150
|
+
def serialize_value(column, v)
|
151
|
+
return v if v.nil?
|
152
|
+
case model.serialization_map[column]
|
153
|
+
when :marshal
|
154
|
+
[Marshal.dump(v)].pack('m')
|
155
|
+
when :yaml
|
156
|
+
v.to_yaml
|
157
|
+
when :json
|
158
|
+
JSON.generate v
|
159
|
+
else
|
160
|
+
raise Error, "Bad serialization format (#{model.serialization_map[column].inspect}) for column #{column.inspect}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|