sequel 3.5.0 → 3.6.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 +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
|