sequel 4.1.1 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -0
- data/doc/opening_databases.rdoc +4 -0
- data/doc/release_notes/4.2.0.txt +129 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
- data/lib/sequel/adapters/mysql2.rb +2 -1
- data/lib/sequel/adapters/postgres.rb +8 -4
- data/lib/sequel/adapters/shared/db2.rb +5 -0
- data/lib/sequel/adapters/shared/mssql.rb +15 -4
- data/lib/sequel/adapters/shared/mysql.rb +1 -0
- data/lib/sequel/adapters/shared/oracle.rb +1 -1
- data/lib/sequel/adapters/shared/postgres.rb +10 -0
- data/lib/sequel/adapters/shared/sqlite.rb +5 -0
- data/lib/sequel/database/features.rb +6 -1
- data/lib/sequel/database/schema_methods.rb +3 -7
- data/lib/sequel/dataset/actions.rb +3 -4
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/misc.rb +28 -3
- data/lib/sequel/dataset/mutation.rb +37 -11
- data/lib/sequel/dataset/prepared_statements.rb +1 -3
- data/lib/sequel/dataset/query.rb +12 -3
- data/lib/sequel/dataset/sql.rb +12 -6
- data/lib/sequel/deprecated.rb +1 -1
- data/lib/sequel/extensions/columns_introspection.rb +1 -1
- data/lib/sequel/extensions/core_extensions.rb +0 -2
- data/lib/sequel/extensions/empty_array_ignore_nulls.rb +1 -1
- data/lib/sequel/extensions/filter_having.rb +1 -1
- data/lib/sequel/extensions/from_block.rb +31 -0
- data/lib/sequel/extensions/graph_each.rb +1 -1
- data/lib/sequel/extensions/hash_aliases.rb +1 -1
- data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +78 -0
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_loose_count.rb +32 -0
- data/lib/sequel/extensions/pg_static_cache_updater.rb +133 -0
- data/lib/sequel/extensions/pretty_table.rb +1 -1
- data/lib/sequel/extensions/query.rb +3 -1
- data/lib/sequel/extensions/query_literals.rb +1 -1
- data/lib/sequel/extensions/select_remove.rb +1 -1
- data/lib/sequel/extensions/sequel_3_dataset_methods.rb +1 -1
- data/lib/sequel/extensions/set_overrides.rb +1 -1
- data/lib/sequel/model.rb +1 -1
- data/lib/sequel/model/base.rb +20 -6
- data/lib/sequel/model/exceptions.rb +1 -1
- data/lib/sequel/plugins/composition.rb +9 -0
- data/lib/sequel/plugins/dirty.rb +19 -8
- data/lib/sequel/plugins/instance_filters.rb +9 -0
- data/lib/sequel/plugins/serialization.rb +9 -0
- data/lib/sequel/plugins/serialization_modification_detection.rb +9 -0
- data/lib/sequel/plugins/static_cache.rb +96 -28
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/mssql_spec.rb +1 -1
- data/spec/adapters/postgres_spec.rb +70 -0
- data/spec/core/dataset_spec.rb +58 -1
- data/spec/core/deprecated_spec.rb +1 -1
- data/spec/core/schema_spec.rb +18 -0
- data/spec/extensions/composition_spec.rb +7 -0
- data/spec/extensions/dirty_spec.rb +9 -0
- data/spec/extensions/from_block_spec.rb +21 -0
- data/spec/extensions/instance_filters_spec.rb +6 -0
- data/spec/extensions/pg_loose_count_spec.rb +17 -0
- data/spec/extensions/pg_static_cache_updater_spec.rb +80 -0
- data/spec/extensions/query_spec.rb +8 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +9 -0
- data/spec/extensions/serialization_spec.rb +7 -0
- data/spec/extensions/set_overrides_spec.rb +12 -0
- data/spec/extensions/static_cache_spec.rb +314 -154
- data/spec/integration/dataset_test.rb +12 -2
- data/spec/integration/schema_test.rb +13 -0
- data/spec/model/record_spec.rb +74 -0
- metadata +13 -3
@@ -0,0 +1,133 @@
|
|
1
|
+
# The pg_static_cache_updater extension is designed to
|
2
|
+
# automatically update the caches in the models using the
|
3
|
+
# static_cache plugin when changes to the underlying tables
|
4
|
+
# are detected.
|
5
|
+
#
|
6
|
+
# Before using the extension in production, you have to add
|
7
|
+
# triggers to the tables for the classes where you want the
|
8
|
+
# caches updated automatically. You would generally do this
|
9
|
+
# during a migration:
|
10
|
+
#
|
11
|
+
# Sequel.migration do
|
12
|
+
# up do
|
13
|
+
# extension :pg_static_cache_updater
|
14
|
+
# create_static_cache_update_function
|
15
|
+
# create_static_cache_update_trigger(:table_1)
|
16
|
+
# create_static_cache_update_trigger(:table_2)
|
17
|
+
# end
|
18
|
+
# down do
|
19
|
+
# extension :pg_static_cache_updater
|
20
|
+
# drop_trigger(:table_2, default_static_cache_update_name)
|
21
|
+
# drop_trigger(:table_1, default_static_cache_update_name)
|
22
|
+
# drop_function(default_static_cache_update_name)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# After the triggers have been added, in your application process,
|
27
|
+
# after setting up your models, you need to listen for changes to
|
28
|
+
# the underlying tables:
|
29
|
+
#
|
30
|
+
# class Model1 < Sequel::Model(:table_1)
|
31
|
+
# plugin :static_cache
|
32
|
+
# end
|
33
|
+
# class Model2 < Sequel::Model(:table_2)
|
34
|
+
# plugin :static_cache
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# DB.extension :pg_static_cache_updater
|
38
|
+
# DB.listen_for_static_cache_updates([Model1, Model2])
|
39
|
+
#
|
40
|
+
# When an INSERT/UPDATE/DELETE happens on the underlying table,
|
41
|
+
# the trigger will send a notification with the table's OID.
|
42
|
+
# The application(s) listening on that channel will receive
|
43
|
+
# the notification, check the oid to see if it matches one
|
44
|
+
# for the model tables it is interested in, and tell that model
|
45
|
+
# to reload the cache if there is a match.
|
46
|
+
#
|
47
|
+
# Note that listen_for_static_cache_updates spawns a new thread
|
48
|
+
# which will reserve its own database connection. This thread
|
49
|
+
# runs until the application process is shutdown.
|
50
|
+
#
|
51
|
+
# Also note that PostgreSQL does not send notifications to
|
52
|
+
# channels until after the transaction including the changes
|
53
|
+
# is committed. Also, because a separate thread is used to
|
54
|
+
# listen for notifications, there may be a slight delay between
|
55
|
+
# when the transaction is committed and when the cache is
|
56
|
+
# reloaded.
|
57
|
+
#
|
58
|
+
# Requirements:
|
59
|
+
# * PostgreSQL 9.0+
|
60
|
+
# * Listening Database object must be using the postgres adapter
|
61
|
+
# with the pg driver (the model classes do not have to
|
62
|
+
# use the same Database).
|
63
|
+
# * Must be using a thread-safe connection pool (the default).
|
64
|
+
|
65
|
+
module Sequel
|
66
|
+
module Postgres
|
67
|
+
module StaticCacheUpdater
|
68
|
+
# Add the static cache update function to the PostgreSQL database.
|
69
|
+
# This must be added before any triggers using this function are
|
70
|
+
# added.
|
71
|
+
#
|
72
|
+
# Options:
|
73
|
+
# :channel_name :: Override the channel name to use.
|
74
|
+
# :function_name :: Override the function name to use.
|
75
|
+
def create_static_cache_update_function(opts=OPTS)
|
76
|
+
create_function(opts[:function_name]||default_static_cache_update_name, <<SQL, :returns=>:trigger, :language=>:plpgsql)
|
77
|
+
BEGIN
|
78
|
+
PERFORM pg_notify(#{literal((opts[:channel_name]||default_static_cache_update_name).to_s)}, TG_RELID::text);
|
79
|
+
RETURN NULL;
|
80
|
+
END
|
81
|
+
SQL
|
82
|
+
end
|
83
|
+
|
84
|
+
# Add a trigger to the given table that calls the function
|
85
|
+
# which will notify about table changes.
|
86
|
+
#
|
87
|
+
# Options:
|
88
|
+
# :function_name :: Override the function name to use.
|
89
|
+
# :trigger_name :: Override the trigger name to use.
|
90
|
+
def create_static_cache_update_trigger(table, opts=OPTS)
|
91
|
+
create_trigger(table, opts[:trigger_name]||default_static_cache_update_name, opts[:function_name]||default_static_cache_update_name, :after=>true)
|
92
|
+
end
|
93
|
+
|
94
|
+
# The default name for the function, trigger, and notification channel
|
95
|
+
# for this extension.
|
96
|
+
def default_static_cache_update_name
|
97
|
+
:sequel_static_cache_update
|
98
|
+
end
|
99
|
+
|
100
|
+
# Listen on the notification channel for changes to any of tables for
|
101
|
+
# the models given. If notified about a change to one of the tables,
|
102
|
+
# reload the cache for the related model. Options given are also
|
103
|
+
# passed to Database#listen.
|
104
|
+
#
|
105
|
+
# Note that this implementation does not currently support model
|
106
|
+
# models that use the same underlying table.
|
107
|
+
#
|
108
|
+
# Options:
|
109
|
+
# :channel_name :: Override the channel name to use.
|
110
|
+
def listen_for_static_cache_updates(models, opts=OPTS)
|
111
|
+
raise Error, "this database object does not respond to listen, use the postgres adapter with the pg driver" unless respond_to?(:listen)
|
112
|
+
models = [models] unless models.is_a?(Array)
|
113
|
+
raise Error, "array of models to listen for changes cannot be empty" if models.empty?
|
114
|
+
|
115
|
+
oid_map = {}
|
116
|
+
models.each do |model|
|
117
|
+
raise Error, "#{model.inspect} does not use the static_cache plugin" unless model.respond_to?(:load_cache, true)
|
118
|
+
oid_map[get(regclass_oid(model.dataset.first_source_table))] = model
|
119
|
+
end
|
120
|
+
|
121
|
+
Thread.new do
|
122
|
+
listen(opts[:channel_name]||default_static_cache_update_name, {:loop=>true}.merge(opts)) do |_, _, oid|
|
123
|
+
if model = oid_map[oid.to_i]
|
124
|
+
model.send(:load_cache)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
Database.register_extension(:pg_static_cache_updater, Postgres::StaticCacheUpdater)
|
133
|
+
end
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# You can load this extension into specific datasets:
|
13
13
|
#
|
14
14
|
# ds = DB[:table]
|
15
|
-
# ds.extension(:pretty_table)
|
15
|
+
# ds = ds.extension(:pretty_table)
|
16
16
|
#
|
17
17
|
# Or you can load it into all of a database's datasets, which
|
18
18
|
# is probably the desired behavior if you are using this extension:
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# You can load this extension into specific datasets:
|
6
6
|
#
|
7
7
|
# ds = DB[:table]
|
8
|
-
# ds.extension(:query)
|
8
|
+
# ds = ds.extension(:query)
|
9
9
|
#
|
10
10
|
# Or you can load it into all of a database's datasets, which
|
11
11
|
# is probably the desired behavior if you are using this extension:
|
@@ -25,6 +25,8 @@ module Sequel
|
|
25
25
|
end
|
26
26
|
|
27
27
|
module DatasetQuery
|
28
|
+
Dataset.def_mutation_method(:query, :module=>self)
|
29
|
+
|
28
30
|
# Translates a query block into a dataset. Query blocks are an
|
29
31
|
# alternative to Sequel's usual method chaining, by using
|
30
32
|
# instance_eval with a proxy object:
|
@@ -24,7 +24,7 @@
|
|
24
24
|
# You can load this extension into specific datasets:
|
25
25
|
#
|
26
26
|
# ds = DB[:table]
|
27
|
-
# ds.extension(:query_literals)
|
27
|
+
# ds = ds.extension(:query_literals)
|
28
28
|
#
|
29
29
|
# Or you can load it into all of a database's datasets, which
|
30
30
|
# is probably the desired behavior if you are using this extension:
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# You can load this extension into specific datasets:
|
6
6
|
#
|
7
7
|
# ds = DB[:table]
|
8
|
-
# ds.extension(:select_remove)
|
8
|
+
# ds = ds.extension(:select_remove)
|
9
9
|
#
|
10
10
|
# Or you can load it into all of a database's datasets, which
|
11
11
|
# is probably the desired behavior if you are using this extension:
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# You can load this extension into specific datasets:
|
13
13
|
#
|
14
14
|
# ds = DB[:table]
|
15
|
-
# ds.extension(:sequel_3_dataset_methods)
|
15
|
+
# ds = ds.extension(:sequel_3_dataset_methods)
|
16
16
|
#
|
17
17
|
# Or you can load it into all of a database's datasets, which
|
18
18
|
# is probably the desired behavior if you are using this extension:
|
@@ -7,7 +7,7 @@
|
|
7
7
|
# You can load this extension into specific datasets:
|
8
8
|
#
|
9
9
|
# ds = DB[:table]
|
10
|
-
# ds.extension(:set_overrides)
|
10
|
+
# ds = ds.extension(:set_overrides)
|
11
11
|
#
|
12
12
|
# Or you can load it into all of a database's datasets, which
|
13
13
|
# is probably the desired behavior if you are using this extension:
|
data/lib/sequel/model.rb
CHANGED
@@ -80,7 +80,7 @@ module Sequel
|
|
80
80
|
|
81
81
|
# Class methods added to model that call the method of the same name on the dataset
|
82
82
|
DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
|
83
|
-
[:each_server]) - [:and, :or, :[], :
|
83
|
+
[:each_server]) - [:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases]
|
84
84
|
|
85
85
|
# Boolean settings that can be modified at the global, class, or instance level.
|
86
86
|
BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
|
data/lib/sequel/model/base.rb
CHANGED
@@ -1001,6 +1001,14 @@ module Sequel
|
|
1001
1001
|
@changed_columns ||= []
|
1002
1002
|
end
|
1003
1003
|
|
1004
|
+
# Similar to Model#dup, but copies frozen status to returned object
|
1005
|
+
# if current object is frozen.
|
1006
|
+
def clone
|
1007
|
+
o = dup
|
1008
|
+
o.freeze if frozen?
|
1009
|
+
o
|
1010
|
+
end
|
1011
|
+
|
1004
1012
|
# Deletes and returns +self+. Does not run destroy hooks.
|
1005
1013
|
# Look into using +destroy+ instead.
|
1006
1014
|
#
|
@@ -1026,6 +1034,18 @@ module Sequel
|
|
1026
1034
|
checked_save_failure(opts){checked_transaction(opts){_destroy(opts)}}
|
1027
1035
|
end
|
1028
1036
|
|
1037
|
+
# Produce a shallow copy of the object, similar to Object#dup.
|
1038
|
+
def dup
|
1039
|
+
s = self
|
1040
|
+
super.instance_eval do
|
1041
|
+
@values = s.values.dup
|
1042
|
+
@changed_columns = s.changed_columns.dup
|
1043
|
+
@errors = s.errors.dup
|
1044
|
+
@this = s.this.dup if !new? && model.primary_key
|
1045
|
+
self
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
|
1029
1049
|
# Iterates through all of the current values using each.
|
1030
1050
|
#
|
1031
1051
|
# Album[1].each{|k, v| puts "#{k} => #{v}"}
|
@@ -1396,12 +1416,6 @@ module Sequel
|
|
1396
1416
|
self
|
1397
1417
|
end
|
1398
1418
|
|
1399
|
-
# REMOVE41
|
1400
|
-
def set_values(hash)
|
1401
|
-
Sequel::Deprecation.deprecate('Model#set_values is deprecreated and will be removed in Sequel 4.1. Please use _refresh_set_values or _save_set_values or set the values directly.')
|
1402
|
-
@values = hash
|
1403
|
-
end
|
1404
|
-
|
1405
1419
|
# Clear the setter_methods cache when a method is added
|
1406
1420
|
def singleton_method_added(meth)
|
1407
1421
|
@singleton_setter_added = true if meth.to_s =~ SETTER_METHOD_REGEXP
|
@@ -11,7 +11,7 @@ module Sequel
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
#
|
14
|
+
# Alias for HookFailed, kept for backwards compatibility
|
15
15
|
BeforeHookFailed = HookFailed
|
16
16
|
|
17
17
|
# Exception class raised when +require_modification+ is set and an UPDATE or DELETE statement to modify the dataset doesn't
|
@@ -161,6 +161,15 @@ module Sequel
|
|
161
161
|
@compositions ||= {}
|
162
162
|
end
|
163
163
|
|
164
|
+
# Duplicate compositions hash when duplicating model instance.
|
165
|
+
def dup
|
166
|
+
s = self
|
167
|
+
super.instance_eval do
|
168
|
+
@compositions = s.compositions.dup
|
169
|
+
self
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
164
173
|
# Freeze compositions hash when freezing model instance.
|
165
174
|
def freeze
|
166
175
|
compositions.freeze
|
data/lib/sequel/plugins/dirty.rb
CHANGED
@@ -85,6 +85,25 @@ module Sequel
|
|
85
85
|
initial_values.has_key?(column)
|
86
86
|
end
|
87
87
|
|
88
|
+
# Duplicate internal data structures
|
89
|
+
def dup
|
90
|
+
s = self
|
91
|
+
super.instance_eval do
|
92
|
+
@initial_values = s.initial_values.dup
|
93
|
+
@missing_initial_values = s.send(:missing_initial_values).dup
|
94
|
+
@previous_changes = s.previous_changes.dup if s.previous_changes
|
95
|
+
self
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Freeze internal data structures
|
100
|
+
def freeze
|
101
|
+
initial_values.freeze
|
102
|
+
missing_initial_values.freeze
|
103
|
+
@previous_changes.freeze if @previous_changes
|
104
|
+
super
|
105
|
+
end
|
106
|
+
|
88
107
|
# The initial value of the given column. If the column value has
|
89
108
|
# not changed, this will be the same as the current value of the
|
90
109
|
# column.
|
@@ -101,14 +120,6 @@ module Sequel
|
|
101
120
|
@initial_values ||= {}
|
102
121
|
end
|
103
122
|
|
104
|
-
# Freeze internal data structures
|
105
|
-
def freeze
|
106
|
-
initial_values.freeze
|
107
|
-
missing_initial_values.freeze
|
108
|
-
@previous_changes.freeze if @previous_changes
|
109
|
-
super
|
110
|
-
end
|
111
|
-
|
112
123
|
# Reset the column to its initial value. If the column was not set
|
113
124
|
# initial, removes it from the values.
|
114
125
|
#
|
@@ -59,6 +59,15 @@ module Sequel
|
|
59
59
|
clear_instance_filters
|
60
60
|
end
|
61
61
|
|
62
|
+
# Duplicate internal structures when duplicating model instance.
|
63
|
+
def dup
|
64
|
+
ifs = instance_filters.dup
|
65
|
+
super.instance_eval do
|
66
|
+
@instance_filters = ifs
|
67
|
+
self
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
62
71
|
# Freeze the instance filters when freezing the object
|
63
72
|
def freeze
|
64
73
|
instance_filters.freeze
|
@@ -172,6 +172,15 @@ module Sequel
|
|
172
172
|
@deserialized_values ||= {}
|
173
173
|
end
|
174
174
|
|
175
|
+
# Freeze the deserialized values
|
176
|
+
def dup
|
177
|
+
dv = deserialized_values.dup
|
178
|
+
super.instance_eval do
|
179
|
+
@deserialized_values = dv
|
180
|
+
self
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
175
184
|
# Freeze the deserialized values
|
176
185
|
def freeze
|
177
186
|
deserialized_values.freeze
|
@@ -45,6 +45,15 @@ module Sequel
|
|
45
45
|
cc
|
46
46
|
end
|
47
47
|
|
48
|
+
# Duplicate the original deserialized values when duplicating instance.
|
49
|
+
def dup
|
50
|
+
o = @original_deserialized_values
|
51
|
+
super.instance_eval do
|
52
|
+
@original_deserialized_values = o.dup if o
|
53
|
+
self
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
48
57
|
# Freeze the original deserialized values when freezing the instance.
|
49
58
|
def freeze
|
50
59
|
@original_deserialized_values ||= {}
|
@@ -3,26 +3,45 @@ module Sequel
|
|
3
3
|
# The static_cache plugin is designed for models that are not modified at all
|
4
4
|
# in production use cases, or at least where modifications to them would usually
|
5
5
|
# coincide with an application restart. When loaded into a model class, it
|
6
|
-
# retrieves all rows in the database and
|
6
|
+
# retrieves all rows in the database and statically caches a ruby array and hash
|
7
7
|
# keyed on primary key containing all of the model instances. All of these instances
|
8
|
-
# are frozen so they won't be modified unexpectedly
|
8
|
+
# are frozen so they won't be modified unexpectedly, and before hooks disallow
|
9
|
+
# saving or destroying instances.
|
10
|
+
#
|
11
|
+
# You can use the :frozen=>false option to have this plugin return unfrozen
|
12
|
+
# instances. This is slower as it requires creating new objects, but it allows
|
13
|
+
# you to make changes to the object and save them. If you set the option to false,
|
14
|
+
# you are responsible for updating the cache manually (the pg_static_cache_updater
|
15
|
+
# extension can handle this automatically).
|
9
16
|
#
|
10
17
|
# The caches this plugin creates are used for the following things:
|
11
18
|
#
|
12
19
|
# * Primary key lookups (e.g. Model[1])
|
13
|
-
# * Model.all
|
14
|
-
# * Model.each
|
15
|
-
# * Model.
|
16
|
-
# * Model.
|
20
|
+
# * Model.all
|
21
|
+
# * Model.each
|
22
|
+
# * Model.count (without an argument or block)
|
23
|
+
# * Model.map
|
24
|
+
# * Model.to_hash
|
25
|
+
# * Model.to_hash_groups
|
17
26
|
#
|
18
27
|
# Usage:
|
19
28
|
#
|
20
|
-
# # Cache the AlbumType class
|
29
|
+
# # Cache the AlbumType class statically, disallowing any changes.
|
21
30
|
# AlbumType.plugin :static_cache
|
31
|
+
#
|
32
|
+
# # Cache the AlbumType class statically, but return unfrozen instances
|
33
|
+
# # that can be modified.
|
34
|
+
# AlbumType.plugin :static_cache, :frozen=>false
|
22
35
|
module StaticCache
|
23
|
-
# Populate the static caches when loading the plugin.
|
24
|
-
|
25
|
-
|
36
|
+
# Populate the static caches when loading the plugin. Options:
|
37
|
+
# :frozen :: Whether retrieved model objects are frozen. The default is true,
|
38
|
+
# for better performance as the shared frozen objects can be used
|
39
|
+
# directly. If set to false, new instances are created.
|
40
|
+
def self.configure(model, opts=OPTS)
|
41
|
+
model.instance_eval do
|
42
|
+
@static_cache_frozen = opts.fetch(:frozen, true)
|
43
|
+
load_cache
|
44
|
+
end
|
26
45
|
end
|
27
46
|
|
28
47
|
module ClassMethods
|
@@ -32,7 +51,11 @@ module Sequel
|
|
32
51
|
# An array of all of the model's frozen instances, without issuing a database
|
33
52
|
# query.
|
34
53
|
def all
|
35
|
-
@
|
54
|
+
if @static_cache_frozen
|
55
|
+
@all.dup
|
56
|
+
else
|
57
|
+
map{|o| o}
|
58
|
+
end
|
36
59
|
end
|
37
60
|
|
38
61
|
# Get the number of records in the cache, without issuing a database query.
|
@@ -47,13 +70,17 @@ module Sequel
|
|
47
70
|
# Return the frozen object with the given pk, or nil if no such object exists
|
48
71
|
# in the cache, without issuing a database query.
|
49
72
|
def cache_get_pk(pk)
|
50
|
-
cache[pk]
|
73
|
+
static_cache_object(cache[pk])
|
51
74
|
end
|
52
75
|
|
53
76
|
# Yield each of the model's frozen instances to the block, without issuing a database
|
54
77
|
# query.
|
55
78
|
def each(&block)
|
56
|
-
@
|
79
|
+
if @static_cache_frozen
|
80
|
+
@all.each(&block)
|
81
|
+
else
|
82
|
+
@all.each{|o| yield(static_cache_object(o))}
|
83
|
+
end
|
57
84
|
end
|
58
85
|
|
59
86
|
# Use the cache instead of a query to get the results.
|
@@ -65,36 +92,47 @@ module Sequel
|
|
65
92
|
else
|
66
93
|
@all.map{|r| r[column]}
|
67
94
|
end
|
95
|
+
elsif @static_cache_frozen
|
96
|
+
@all.map(&block)
|
97
|
+
elsif block
|
98
|
+
@all.map{|o| yield(static_cache_object(o))}
|
68
99
|
else
|
69
|
-
|
100
|
+
all.map
|
70
101
|
end
|
71
102
|
end
|
72
103
|
|
73
104
|
Plugins.after_set_dataset(self, :load_cache)
|
105
|
+
Plugins.inherited_instance_variables(self, :@static_cache_frozen=>nil)
|
74
106
|
|
75
107
|
# Use the cache instead of a query to get the results.
|
76
108
|
def to_hash(key_column = nil, value_column = nil)
|
77
|
-
|
109
|
+
if key_column.nil? && value_column.nil?
|
110
|
+
if @static_cache_frozen
|
111
|
+
return cache.dup
|
112
|
+
else
|
113
|
+
key_column = primary_key
|
114
|
+
end
|
115
|
+
end
|
78
116
|
|
79
117
|
h = {}
|
80
118
|
if value_column
|
81
119
|
if value_column.is_a?(Array)
|
82
120
|
if key_column.is_a?(Array)
|
83
|
-
each{|r| h[r.values.values_at(*key_column)] = r.values.values_at(*value_column)}
|
121
|
+
@all.each{|r| h[r.values.values_at(*key_column)] = r.values.values_at(*value_column)}
|
84
122
|
else
|
85
|
-
each{|r| h[r[key_column]] = r.values.values_at(*value_column)}
|
123
|
+
@all.each{|r| h[r[key_column]] = r.values.values_at(*value_column)}
|
86
124
|
end
|
87
125
|
else
|
88
126
|
if key_column.is_a?(Array)
|
89
|
-
each{|r| h[r.values.values_at(*key_column)] = r[value_column]}
|
127
|
+
@all.each{|r| h[r.values.values_at(*key_column)] = r[value_column]}
|
90
128
|
else
|
91
|
-
each{|r| h[r[key_column]] = r[value_column]}
|
129
|
+
@all.each{|r| h[r[key_column]] = r[value_column]}
|
92
130
|
end
|
93
131
|
end
|
94
132
|
elsif key_column.is_a?(Array)
|
95
|
-
each{|r| h[r.values.values_at(*key_column)] = r}
|
133
|
+
@all.each{|r| h[r.values.values_at(*key_column)] = static_cache_object(r)}
|
96
134
|
else
|
97
|
-
each{|r| h[r[key_column]] = r}
|
135
|
+
@all.each{|r| h[r[key_column]] = static_cache_object(r)}
|
98
136
|
end
|
99
137
|
h
|
100
138
|
end
|
@@ -105,31 +143,36 @@ module Sequel
|
|
105
143
|
if value_column
|
106
144
|
if value_column.is_a?(Array)
|
107
145
|
if key_column.is_a?(Array)
|
108
|
-
each{|r| (h[r.values.values_at(*key_column)] ||= []) << r.values.values_at(*value_column)}
|
146
|
+
@all.each{|r| (h[r.values.values_at(*key_column)] ||= []) << r.values.values_at(*value_column)}
|
109
147
|
else
|
110
|
-
each{|r| (h[r[key_column]] ||= []) << r.values.values_at(*value_column)}
|
148
|
+
@all.each{|r| (h[r[key_column]] ||= []) << r.values.values_at(*value_column)}
|
111
149
|
end
|
112
150
|
else
|
113
151
|
if key_column.is_a?(Array)
|
114
|
-
each{|r| (h[r.values.values_at(*key_column)] ||= []) << r[value_column]}
|
152
|
+
@all.each{|r| (h[r.values.values_at(*key_column)] ||= []) << r[value_column]}
|
115
153
|
else
|
116
|
-
each{|r| (h[r[key_column]] ||= []) << r[value_column]}
|
154
|
+
@all.each{|r| (h[r[key_column]] ||= []) << r[value_column]}
|
117
155
|
end
|
118
156
|
end
|
119
157
|
elsif key_column.is_a?(Array)
|
120
|
-
each{|r| (h[r.values.values_at(*key_column)] ||= []) << r}
|
158
|
+
@all.each{|r| (h[r.values.values_at(*key_column)] ||= []) << static_cache_object(r)}
|
121
159
|
else
|
122
|
-
each{|r| (h[r[key_column]] ||= []) << r}
|
160
|
+
@all.each{|r| (h[r[key_column]] ||= []) << static_cache_object(r)}
|
123
161
|
end
|
124
162
|
h
|
125
163
|
end
|
126
164
|
|
165
|
+
# Ask whether modifications to this class are allowed.
|
166
|
+
def static_cache_allow_modifications?
|
167
|
+
!@static_cache_frozen
|
168
|
+
end
|
169
|
+
|
127
170
|
private
|
128
171
|
|
129
172
|
# Return the frozen object with the given pk, or nil if no such object exists
|
130
173
|
# in the cache, without issuing a database query.
|
131
174
|
def primary_key_lookup(pk)
|
132
|
-
cache[pk]
|
175
|
+
static_cache_object(cache[pk])
|
133
176
|
end
|
134
177
|
|
135
178
|
# Reload the cache for this model by retrieving all of the instances in the dataset
|
@@ -141,6 +184,31 @@ module Sequel
|
|
141
184
|
@all = a.freeze
|
142
185
|
@cache = h.freeze
|
143
186
|
end
|
187
|
+
|
188
|
+
# If :frozen=>false is not used, just return the argument. Otherwise,
|
189
|
+
# create a new instance with the arguments values if the argument is
|
190
|
+
# not nil.
|
191
|
+
def static_cache_object(o)
|
192
|
+
if @static_cache_frozen
|
193
|
+
o
|
194
|
+
elsif o
|
195
|
+
call(o.values.dup)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
module InstanceMethods
|
201
|
+
# Disallowing destroying the object unless the :frozen=>false option was used.
|
202
|
+
def before_destroy
|
203
|
+
return false unless model.static_cache_allow_modifications?
|
204
|
+
super
|
205
|
+
end
|
206
|
+
|
207
|
+
# Disallowing saving the object unless the :frozen=>false option was used.
|
208
|
+
def before_save
|
209
|
+
return false unless model.static_cache_allow_modifications?
|
210
|
+
super
|
211
|
+
end
|
144
212
|
end
|
145
213
|
end
|
146
214
|
end
|