sequel 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +108 -0
- data/README.rdoc +25 -14
- data/Rakefile +20 -1
- data/doc/advanced_associations.rdoc +61 -64
- data/doc/cheat_sheet.rdoc +16 -7
- data/doc/opening_databases.rdoc +3 -3
- data/doc/prepared_statements.rdoc +1 -1
- data/doc/reflection.rdoc +2 -1
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/schema.rdoc +19 -14
- data/lib/sequel/adapters/amalgalite.rb +5 -27
- data/lib/sequel/adapters/jdbc.rb +13 -3
- data/lib/sequel/adapters/jdbc/h2.rb +17 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
- data/lib/sequel/adapters/mysql.rb +4 -3
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +87 -28
- data/lib/sequel/adapters/shared/mssql.rb +47 -6
- data/lib/sequel/adapters/shared/mysql.rb +12 -31
- data/lib/sequel/adapters/shared/postgres.rb +15 -12
- data/lib/sequel/adapters/shared/sqlite.rb +18 -0
- data/lib/sequel/adapters/sqlite.rb +1 -16
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -0
- data/lib/sequel/database/schema_sql.rb +1 -1
- data/lib/sequel/dataset.rb +5 -179
- data/lib/sequel/dataset/actions.rb +123 -0
- data/lib/sequel/dataset/convenience.rb +18 -10
- data/lib/sequel/dataset/features.rb +65 -0
- data/lib/sequel/dataset/prepared_statements.rb +29 -23
- data/lib/sequel/dataset/query.rb +429 -0
- data/lib/sequel/dataset/sql.rb +67 -435
- data/lib/sequel/model/associations.rb +77 -13
- data/lib/sequel/model/base.rb +30 -8
- data/lib/sequel/model/errors.rb +4 -4
- data/lib/sequel/plugins/caching.rb +38 -15
- data/lib/sequel/plugins/force_encoding.rb +18 -7
- data/lib/sequel/plugins/hook_class_methods.rb +4 -0
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +40 -11
- data/lib/sequel/plugins/serialization.rb +17 -3
- data/lib/sequel/plugins/validation_helpers.rb +65 -18
- data/lib/sequel/sql.rb +23 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +96 -10
- data/spec/adapters/mysql_spec.rb +19 -0
- data/spec/adapters/postgres_spec.rb +65 -2
- data/spec/adapters/sqlite_spec.rb +10 -0
- data/spec/core/core_sql_spec.rb +9 -0
- data/spec/core/database_spec.rb +8 -4
- data/spec/core/dataset_spec.rb +122 -29
- data/spec/core/expression_filters_spec.rb +17 -0
- data/spec/extensions/caching_spec.rb +43 -3
- data/spec/extensions/force_encoding_spec.rb +43 -1
- data/spec/extensions/nested_attributes_spec.rb +55 -2
- data/spec/extensions/validation_helpers_spec.rb +71 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/dataset_test.rb +383 -9
- data/spec/integration/eager_loader_test.rb +0 -65
- data/spec/integration/model_test.rb +110 -0
- data/spec/integration/plugin_test.rb +306 -0
- data/spec/integration/prepared_statement_test.rb +32 -0
- data/spec/integration/schema_test.rb +8 -3
- data/spec/integration/spec_helper.rb +1 -25
- data/spec/model/association_reflection_spec.rb +38 -0
- data/spec/model/associations_spec.rb +184 -8
- data/spec/model/eager_loading_spec.rb +23 -0
- data/spec/model/model_spec.rb +8 -0
- data/spec/model/record_spec.rb +84 -1
- metadata +9 -2
@@ -56,7 +56,13 @@ module Sequel
|
|
56
56
|
def associated_class
|
57
57
|
self[:class] ||= constantize(self[:class_name])
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
|
+
# Whether this association can have associated objects, given the current
|
61
|
+
# object. Should be false if obj cannot have associated objects because
|
62
|
+
# the necessary key columns are NULL.
|
63
|
+
def can_have_associated_objects?(obj)
|
64
|
+
true
|
65
|
+
end
|
60
66
|
|
61
67
|
# Name symbol for the dataset association method
|
62
68
|
def dataset_method
|
@@ -126,6 +132,11 @@ module Sequel
|
|
126
132
|
:"remove_#{singularize(self[:name])}"
|
127
133
|
end
|
128
134
|
|
135
|
+
# Whether to check that an object to be disassociated is already associated to this object, false by default.
|
136
|
+
def remove_should_check_existing?
|
137
|
+
false
|
138
|
+
end
|
139
|
+
|
129
140
|
# Whether this association returns an array of objects instead of a single object,
|
130
141
|
# true by default.
|
131
142
|
def returns_array?
|
@@ -152,6 +163,12 @@ module Sequel
|
|
152
163
|
class ManyToOneAssociationReflection < AssociationReflection
|
153
164
|
ASSOCIATION_TYPES[:many_to_one] = self
|
154
165
|
|
166
|
+
# many_to_one associations can only have associated objects if none of
|
167
|
+
# the :keys options have a nil value.
|
168
|
+
def can_have_associated_objects?(obj)
|
169
|
+
!self[:keys].any?{|k| obj.send(k).nil?}
|
170
|
+
end
|
171
|
+
|
155
172
|
# Whether the dataset needs a primary key to function, false for many_to_one associations.
|
156
173
|
def dataset_need_primary_key?
|
157
174
|
false
|
@@ -183,6 +200,7 @@ module Sequel
|
|
183
200
|
def primary_keys
|
184
201
|
self[:primary_keys] ||= Array(primary_key)
|
185
202
|
end
|
203
|
+
alias associated_object_keys primary_keys
|
186
204
|
|
187
205
|
# Whether this association returns an array of objects instead of a single object,
|
188
206
|
# false for a many_to_one association.
|
@@ -200,6 +218,17 @@ module Sequel
|
|
200
218
|
|
201
219
|
class OneToManyAssociationReflection < AssociationReflection
|
202
220
|
ASSOCIATION_TYPES[:one_to_many] = self
|
221
|
+
|
222
|
+
# The keys in the associated model's table related to this association
|
223
|
+
def associated_object_keys
|
224
|
+
self[:keys]
|
225
|
+
end
|
226
|
+
|
227
|
+
# one_to_many associations can only have associated objects if none of
|
228
|
+
# the :keys options have a nil value.
|
229
|
+
def can_have_associated_objects?(obj)
|
230
|
+
!self[:primary_keys].any?{|k| obj.send(k).nil?}
|
231
|
+
end
|
203
232
|
|
204
233
|
# Default foreign key name symbol for key in associated table that points to
|
205
234
|
# current table's primary key.
|
@@ -224,6 +253,11 @@ module Sequel
|
|
224
253
|
false
|
225
254
|
end
|
226
255
|
|
256
|
+
# The one_to_many association needs to check that an object to be removed already is associated.
|
257
|
+
def remove_should_check_existing?
|
258
|
+
true
|
259
|
+
end
|
260
|
+
|
227
261
|
private
|
228
262
|
|
229
263
|
# The reciprocal type of a one_to_many association is a many_to_one association.
|
@@ -249,6 +283,12 @@ module Sequel
|
|
249
283
|
def associated_key_table
|
250
284
|
self[:join_table]
|
251
285
|
end
|
286
|
+
|
287
|
+
# many_to_many associations can only have associated objects if none of
|
288
|
+
# the :left_primary_keys options have a nil value.
|
289
|
+
def can_have_associated_objects?(obj)
|
290
|
+
!self[:left_primary_keys].any?{|k| obj.send(k).nil?}
|
291
|
+
end
|
252
292
|
|
253
293
|
# The default associated key alias(es) to use when eager loading
|
254
294
|
# associations via eager.
|
@@ -314,6 +354,7 @@ module Sequel
|
|
314
354
|
def right_primary_keys
|
315
355
|
self[:right_primary_keys] ||= Array(right_primary_key)
|
316
356
|
end
|
357
|
+
alias associated_object_keys right_primary_keys
|
317
358
|
|
318
359
|
# The columns to select when loading the association, associated_class.table_name.* by default.
|
319
360
|
def select
|
@@ -432,6 +473,8 @@ module Sequel
|
|
432
473
|
# - :conditions - The conditions to use to filter the association, can be any argument passed to filter.
|
433
474
|
# - :dataset - A proc that is instance_evaled to get the base dataset
|
434
475
|
# to use for the _dataset method (before the other options are applied).
|
476
|
+
# - :distinct - Use the DISTINCT clause when selecting associating object, both when
|
477
|
+
# lazy loading and eager loading via .eager (but not when using .eager_graph).
|
435
478
|
# - :eager - The associations to eagerly load via #eager when loading the associated object(s).
|
436
479
|
# For many_to_one associations, this is ignored unless this association is
|
437
480
|
# being eagerly loaded, as it doesn't save queries unless multiple objects
|
@@ -597,6 +640,7 @@ module Sequel
|
|
597
640
|
end
|
598
641
|
ds = ds.order(*opts[:order]) if opts[:order]
|
599
642
|
ds = ds.eager(opts[:eager]) if opts[:eager]
|
643
|
+
ds = ds.distinct if opts[:distinct]
|
600
644
|
if opts[:eager_graph]
|
601
645
|
ds = ds.eager_graph(opts[:eager_graph])
|
602
646
|
ds = ds.add_graph_aliases(opts.associated_key_alias=>[opts.associated_class.table_name, opts.associated_key_alias, SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column)]) if opts.eager_loading_use_associated_key?
|
@@ -903,7 +947,7 @@ module Sequel
|
|
903
947
|
|
904
948
|
# Backbone behind association dataset methods
|
905
949
|
def _dataset(opts)
|
906
|
-
raise(Sequel::Error, "model object #{
|
950
|
+
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
|
907
951
|
ds = send(opts._dataset_method)
|
908
952
|
ds.extend(AssociationDatasetMethods)
|
909
953
|
ds.model_object = self
|
@@ -916,6 +960,7 @@ module Sequel
|
|
916
960
|
ds = ds.order(*opts[:order]) if opts[:order]
|
917
961
|
ds = ds.limit(*opts[:limit]) if opts[:limit]
|
918
962
|
ds = ds.eager(*opts[:eager]) if opts[:eager]
|
963
|
+
ds = ds.distinct if opts[:distinct]
|
919
964
|
ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
|
920
965
|
ds = send(opts.dataset_helper_method, ds) if opts[:block]
|
921
966
|
ds
|
@@ -924,11 +969,11 @@ module Sequel
|
|
924
969
|
# Return the associated objects from the dataset, without callbacks, reciprocals, and caching.
|
925
970
|
def _load_associated_objects(opts)
|
926
971
|
if opts.returns_array?
|
927
|
-
send(opts.dataset_method).all
|
972
|
+
opts.can_have_associated_objects?(self) ? send(opts.dataset_method).all : []
|
928
973
|
else
|
929
974
|
if !opts[:key]
|
930
975
|
send(opts.dataset_method).all.first
|
931
|
-
elsif opts
|
976
|
+
elsif opts.can_have_associated_objects?(self)
|
932
977
|
send(opts.dataset_method).first
|
933
978
|
end
|
934
979
|
end
|
@@ -936,14 +981,22 @@ module Sequel
|
|
936
981
|
|
937
982
|
# Add the given associated object to the given association
|
938
983
|
def add_associated_object(opts, o, *args)
|
939
|
-
|
984
|
+
klass = opts.associated_class
|
985
|
+
if o.is_a?(Hash)
|
986
|
+
o = klass.new(o)
|
987
|
+
elsif !o.is_a?(klass)
|
988
|
+
raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
|
989
|
+
end
|
990
|
+
raise(Sequel::Error, "model object #{inspect} does not have a primary key") unless pk
|
940
991
|
if opts.need_associated_primary_key?
|
941
992
|
o.save(:validate=>opts[:validate]) if o.new?
|
942
|
-
raise(Sequel::Error, "associated object #{o.
|
993
|
+
raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") unless o.pk
|
943
994
|
end
|
944
995
|
return if run_association_callbacks(opts, :before_add, o) == false
|
945
996
|
send(opts._add_method, o, *args)
|
946
|
-
associations[opts[:name]]
|
997
|
+
if array = associations[opts[:name]] and !array.include?(o)
|
998
|
+
array.push(o)
|
999
|
+
end
|
947
1000
|
add_reciprocal_object(opts, o)
|
948
1001
|
run_association_callbacks(opts, :after_add, o)
|
949
1002
|
o
|
@@ -982,7 +1035,7 @@ module Sequel
|
|
982
1035
|
|
983
1036
|
# Remove all associated objects from the given association
|
984
1037
|
def remove_all_associated_objects(opts, *args)
|
985
|
-
raise(Sequel::Error, "model object #{
|
1038
|
+
raise(Sequel::Error, "model object #{inspect} does not have a primary key") unless pk
|
986
1039
|
send(opts._remove_all_method, *args)
|
987
1040
|
ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name])
|
988
1041
|
associations[opts[:name]] = []
|
@@ -991,8 +1044,18 @@ module Sequel
|
|
991
1044
|
|
992
1045
|
# Remove the given associated object from the given association
|
993
1046
|
def remove_associated_object(opts, o, *args)
|
994
|
-
|
995
|
-
|
1047
|
+
klass = opts.associated_class
|
1048
|
+
if o.is_a?(Integer) || o.is_a?(String) || o.is_a?(Array)
|
1049
|
+
key = o
|
1050
|
+
pkh = klass.primary_key_hash(key)
|
1051
|
+
raise(Sequel::Error, "no object with key(s) #{key} is currently associated to #{inspect}") unless o = (opts.remove_should_check_existing? ? send(opts.dataset_method) : klass).first(pkh)
|
1052
|
+
elsif !o.is_a?(klass)
|
1053
|
+
raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
|
1054
|
+
elsif opts.remove_should_check_existing? && send(opts.dataset_method).filter(o.pk_hash).empty?
|
1055
|
+
raise(Sequel::Error, "associated object #{o.inspect} is not currently associated to #{inspect}")
|
1056
|
+
end
|
1057
|
+
raise(Sequel::Error, "model object #{inspect} does not have a primary key") unless pk
|
1058
|
+
raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
|
996
1059
|
return if run_association_callbacks(opts, :before_remove, o) == false
|
997
1060
|
send(opts._remove_method, o, *args)
|
998
1061
|
associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name])
|
@@ -1027,7 +1090,7 @@ module Sequel
|
|
1027
1090
|
raise Error, "callbacks should either be Procs or Symbols"
|
1028
1091
|
end
|
1029
1092
|
if res == false and stop_on_false
|
1030
|
-
raise(BeforeHookFailed, "Unable to modify association for
|
1093
|
+
raise(BeforeHookFailed, "Unable to modify association for #{inspect}: one of the #{callback_type} hooks returned false") if raise_error
|
1031
1094
|
return false
|
1032
1095
|
end
|
1033
1096
|
end
|
@@ -1035,7 +1098,7 @@ module Sequel
|
|
1035
1098
|
|
1036
1099
|
# Set the given object as the associated object for the given association
|
1037
1100
|
def set_associated_object(opts, o)
|
1038
|
-
raise(Sequel::Error, "
|
1101
|
+
raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk
|
1039
1102
|
old_val = send(opts.association_method)
|
1040
1103
|
return o if old_val == o
|
1041
1104
|
return if old_val and run_association_callbacks(opts, :before_remove, old_val) == false
|
@@ -1151,6 +1214,7 @@ module Sequel
|
|
1151
1214
|
# call each, you will get a normal graphed result back (a hash with model object values).
|
1152
1215
|
def eager_graph(*associations)
|
1153
1216
|
table_name = model.table_name
|
1217
|
+
|
1154
1218
|
ds = if @opts[:eager_graph]
|
1155
1219
|
self
|
1156
1220
|
else
|
@@ -1159,7 +1223,7 @@ module Sequel
|
|
1159
1223
|
# :requirements - array of requirements for this association
|
1160
1224
|
# :alias_association_type_map - the type of association for this association
|
1161
1225
|
# :alias_association_name_map - the name of the association for this association
|
1162
|
-
clone(:eager_graph=>{:requirements=>{}, :master=>
|
1226
|
+
clone(:eager_graph=>{:requirements=>{}, :master=>table_name, :alias_association_type_map=>{}, :alias_association_name_map=>{}, :reciprocals=>{}, :cartesian_product_number=>0})
|
1163
1227
|
end
|
1164
1228
|
ds.eager_graph_associations(ds, model, table_name, [], *associations)
|
1165
1229
|
end
|
data/lib/sequel/model/base.rb
CHANGED
@@ -173,6 +173,7 @@ module Sequel
|
|
173
173
|
# is created. Also, make sure the inherited class instance variables
|
174
174
|
# are copied into the subclass.
|
175
175
|
def inherited(subclass)
|
176
|
+
super
|
176
177
|
ivs = subclass.instance_variables.collect{|x| x.to_s}
|
177
178
|
EMPTY_INSTANCE_VARIABLES.each{|iv| subclass.instance_variable_set(iv, nil) unless ivs.include?(iv.to_s)}
|
178
179
|
INHERITED_INSTANCE_VARIABLES.each do |iv, dup|
|
@@ -357,7 +358,7 @@ module Sequel
|
|
357
358
|
|
358
359
|
# Returns name of primary table for the dataset.
|
359
360
|
def table_name
|
360
|
-
dataset.
|
361
|
+
dataset.first_source_alias
|
361
362
|
end
|
362
363
|
|
363
364
|
# Allow the setting of the primary key(s) inside new/set/update.
|
@@ -524,10 +525,11 @@ module Sequel
|
|
524
525
|
def initialize(values = {}, from_db = false)
|
525
526
|
if from_db
|
526
527
|
@new = false
|
527
|
-
|
528
|
+
set_values(values)
|
528
529
|
else
|
529
530
|
@values = {}
|
530
531
|
@new = true
|
532
|
+
@modified = true
|
531
533
|
set(values)
|
532
534
|
changed_columns.clear
|
533
535
|
yield self if block_given?
|
@@ -653,11 +655,23 @@ module Sequel
|
|
653
655
|
@values.keys
|
654
656
|
end
|
655
657
|
|
658
|
+
# Remove elements of the model object that make marshalling fail. Returns self.
|
659
|
+
def marshallable!
|
660
|
+
@this = nil
|
661
|
+
self
|
662
|
+
end
|
663
|
+
|
664
|
+
# Explicitly mark the object as modified, so save_changes/update will
|
665
|
+
# run callbacks even if no columns have changed.
|
666
|
+
def modified!
|
667
|
+
@modified = true
|
668
|
+
end
|
669
|
+
|
656
670
|
# Whether this object has been modified since last saved, used by
|
657
671
|
# save_changes to determine whether changes should be saved. New
|
658
672
|
# values are always considered modified.
|
659
673
|
def modified?
|
660
|
-
|
674
|
+
@modified || !changed_columns.empty?
|
661
675
|
end
|
662
676
|
|
663
677
|
# Returns true if the current instance represents a new record.
|
@@ -715,8 +729,8 @@ module Sequel
|
|
715
729
|
# Saves only changed columns if the object has been modified.
|
716
730
|
# If the object has not been modified, returns nil. If unable to
|
717
731
|
# save, returns false unless raise_on_save_failure is true.
|
718
|
-
def save_changes
|
719
|
-
save(:changed=>true) || false if modified?
|
732
|
+
def save_changes(opts={})
|
733
|
+
save(opts.merge(:changed=>true)) || false if modified?
|
720
734
|
end
|
721
735
|
|
722
736
|
# Updates the instance with the supplied values with support for virtual
|
@@ -750,7 +764,7 @@ module Sequel
|
|
750
764
|
@this ||= model.dataset.filter(pk_hash).limit(1).naked
|
751
765
|
end
|
752
766
|
|
753
|
-
# Runs set with the passed hash and runs save_changes
|
767
|
+
# Runs set with the passed hash and then runs save_changes.
|
754
768
|
def update(hash)
|
755
769
|
update_restricted(hash, nil, nil)
|
756
770
|
end
|
@@ -821,7 +835,7 @@ module Sequel
|
|
821
835
|
# Refresh using a particular dataset, used inside save to make sure the same server
|
822
836
|
# is used for reading newly inserted values from the database
|
823
837
|
def _refresh(dataset)
|
824
|
-
|
838
|
+
set_values(dataset.first || raise(Error, "Record not found"))
|
825
839
|
changed_columns.clear
|
826
840
|
associations.clear
|
827
841
|
self
|
@@ -844,6 +858,8 @@ module Sequel
|
|
844
858
|
ds = this
|
845
859
|
ds = ds.server(:default) unless ds.opts[:server]
|
846
860
|
_refresh(ds)
|
861
|
+
else
|
862
|
+
changed_columns.clear
|
847
863
|
end
|
848
864
|
else
|
849
865
|
return save_failure(:update) if before_update == false
|
@@ -860,6 +876,7 @@ module Sequel
|
|
860
876
|
after_save
|
861
877
|
@columns_updated = nil
|
862
878
|
end
|
879
|
+
@modified = false
|
863
880
|
self
|
864
881
|
end
|
865
882
|
|
@@ -897,7 +914,12 @@ module Sequel
|
|
897
914
|
end
|
898
915
|
self
|
899
916
|
end
|
900
|
-
|
917
|
+
|
918
|
+
# Replace the current values with hash.
|
919
|
+
def set_values(hash)
|
920
|
+
@values = hash
|
921
|
+
end
|
922
|
+
|
901
923
|
# Returns all methods that can be used for attribute
|
902
924
|
# assignment (those that end with =), modified by the only
|
903
925
|
# and except arguments:
|
data/lib/sequel/model/errors.rb
CHANGED
@@ -3,11 +3,11 @@ module Sequel
|
|
3
3
|
# Errors represents validation errors, a simple hash subclass
|
4
4
|
# with a few convenience methods.
|
5
5
|
class Errors < ::Hash
|
6
|
-
ATTRIBUTE_JOINER = ' and '
|
6
|
+
ATTRIBUTE_JOINER = ' and '.freeze
|
7
7
|
|
8
8
|
# Assign an array of messages for each attribute on access
|
9
|
-
def
|
10
|
-
|
9
|
+
def [](k)
|
10
|
+
has_key?(k) ? super : (self[k] = [])
|
11
11
|
end
|
12
12
|
|
13
13
|
# Adds an error for the given attribute.
|
@@ -32,7 +32,7 @@ module Sequel
|
|
32
32
|
# Returns the array of errors for the given attribute, or nil
|
33
33
|
# if there are no errors for the attribute.
|
34
34
|
def on(att)
|
35
|
-
self[att] if
|
35
|
+
self[att] if has_key?(att)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Sequel
|
2
2
|
module Plugins
|
3
3
|
# Sequel's built-in caching plugin supports caching to any object that
|
4
|
-
# implements the Ruby-Memcache API
|
5
|
-
# or for all models via:
|
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
6
|
#
|
7
7
|
# Model.plugin :caching, store # Cache all models
|
8
8
|
# MyModel.plugin :caching, store # Just cache MyModel
|
@@ -11,10 +11,15 @@ module Sequel
|
|
11
11
|
#
|
12
12
|
# cache_store.set(key, obj, time) # Associate the obj with the given key
|
13
13
|
# # in the cache for the time (specified
|
14
|
-
# # in seconds)
|
15
|
-
# cache_store.get(key) => obj
|
16
|
-
# cache_store.get(key2) => nil
|
17
|
-
#
|
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
|
+
#
|
19
|
+
# If the :ignore_exceptions option is true, exceptions raised by cache_store.get
|
20
|
+
# are ignored and nil is returned instead. The memcached API is to
|
21
|
+
# raise an exception for a missing record, so if you use memcached, you will
|
22
|
+
# want to use this option.
|
18
23
|
module Caching
|
19
24
|
# Set the cache_store and cache_ttl attributes for the given model.
|
20
25
|
# If the :ttl option is not given, 3600 seconds is the default.
|
@@ -22,12 +27,16 @@ module Sequel
|
|
22
27
|
model.instance_eval do
|
23
28
|
@cache_store = store
|
24
29
|
@cache_ttl = opts[:ttl] || 3600
|
30
|
+
@cache_ignore_exceptions = opts[:ignore_exceptions]
|
25
31
|
end
|
26
32
|
end
|
27
33
|
|
28
34
|
module ClassMethods
|
35
|
+
# If true, ignores exceptions when gettings cached records (the memcached API).
|
36
|
+
attr_reader :cache_ignore_exceptions
|
37
|
+
|
29
38
|
# The cache store object for the model, which should implement the
|
30
|
-
# Ruby-Memcache API
|
39
|
+
# Ruby-Memcache (or memcached) API
|
31
40
|
attr_reader :cache_store
|
32
41
|
|
33
42
|
# The time to live for the cache store, in seconds.
|
@@ -38,38 +47,52 @@ module Sequel
|
|
38
47
|
@cache_ttl = ttl
|
39
48
|
end
|
40
49
|
|
41
|
-
# Copy the
|
50
|
+
# Copy the necessary class instance variables to the subclass.
|
42
51
|
def inherited(subclass)
|
43
52
|
super
|
44
53
|
store = @cache_store
|
45
54
|
ttl = @cache_ttl
|
55
|
+
cache_ignore_exceptions = @cache_ignore_exceptions
|
46
56
|
subclass.instance_eval do
|
47
57
|
@cache_store = store
|
48
58
|
@cache_ttl = ttl
|
59
|
+
@cache_ignore_exceptions = cache_ignore_exceptions
|
49
60
|
end
|
50
61
|
end
|
51
62
|
|
52
63
|
private
|
53
64
|
|
54
65
|
# Delete the entry with the matching key from the cache
|
55
|
-
def cache_delete(
|
56
|
-
@cache_store.delete(
|
66
|
+
def cache_delete(ck)
|
67
|
+
@cache_store.delete(ck)
|
57
68
|
nil
|
58
69
|
end
|
70
|
+
|
71
|
+
def cache_get(ck)
|
72
|
+
if @cache_ignore_exceptions
|
73
|
+
@cache_store.get(ck) rescue nil
|
74
|
+
else
|
75
|
+
@cache_store.get(ck)
|
76
|
+
end
|
77
|
+
end
|
59
78
|
|
60
79
|
# Return a key string for the pk
|
61
80
|
def cache_key(pk)
|
62
81
|
"#{self}:#{Array(pk).join(',')}"
|
63
82
|
end
|
64
83
|
|
84
|
+
# Set the object in the cache_store with the given key for cache_ttl seconds.
|
85
|
+
def cache_set(ck, obj)
|
86
|
+
@cache_store.set(ck, obj, @cache_ttl)
|
87
|
+
end
|
88
|
+
|
65
89
|
# Check the cache before a database lookup unless a hash is supplied.
|
66
90
|
def primary_key_lookup(pk)
|
67
91
|
ck = cache_key(pk)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
@cache_store.set(ck, obj, @cache_ttl)
|
92
|
+
unless obj = cache_get(ck)
|
93
|
+
if obj = super(pk)
|
94
|
+
cache_set(ck, obj)
|
95
|
+
end
|
73
96
|
end
|
74
97
|
obj
|
75
98
|
end
|