sequel 4.1.1 → 4.2.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.
- 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
|