sequel 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/CHANGELOG +108 -0
  2. data/README.rdoc +25 -14
  3. data/Rakefile +20 -1
  4. data/doc/advanced_associations.rdoc +61 -64
  5. data/doc/cheat_sheet.rdoc +16 -7
  6. data/doc/opening_databases.rdoc +3 -3
  7. data/doc/prepared_statements.rdoc +1 -1
  8. data/doc/reflection.rdoc +2 -1
  9. data/doc/release_notes/3.6.0.txt +366 -0
  10. data/doc/schema.rdoc +19 -14
  11. data/lib/sequel/adapters/amalgalite.rb +5 -27
  12. data/lib/sequel/adapters/jdbc.rb +13 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -0
  14. data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
  15. data/lib/sequel/adapters/mysql.rb +4 -3
  16. data/lib/sequel/adapters/oracle.rb +1 -1
  17. data/lib/sequel/adapters/postgres.rb +87 -28
  18. data/lib/sequel/adapters/shared/mssql.rb +47 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +12 -31
  20. data/lib/sequel/adapters/shared/postgres.rb +15 -12
  21. data/lib/sequel/adapters/shared/sqlite.rb +18 -0
  22. data/lib/sequel/adapters/sqlite.rb +1 -16
  23. data/lib/sequel/connection_pool.rb +1 -1
  24. data/lib/sequel/core.rb +1 -1
  25. data/lib/sequel/database.rb +1 -1
  26. data/lib/sequel/database/schema_generator.rb +2 -0
  27. data/lib/sequel/database/schema_sql.rb +1 -1
  28. data/lib/sequel/dataset.rb +5 -179
  29. data/lib/sequel/dataset/actions.rb +123 -0
  30. data/lib/sequel/dataset/convenience.rb +18 -10
  31. data/lib/sequel/dataset/features.rb +65 -0
  32. data/lib/sequel/dataset/prepared_statements.rb +29 -23
  33. data/lib/sequel/dataset/query.rb +429 -0
  34. data/lib/sequel/dataset/sql.rb +67 -435
  35. data/lib/sequel/model/associations.rb +77 -13
  36. data/lib/sequel/model/base.rb +30 -8
  37. data/lib/sequel/model/errors.rb +4 -4
  38. data/lib/sequel/plugins/caching.rb +38 -15
  39. data/lib/sequel/plugins/force_encoding.rb +18 -7
  40. data/lib/sequel/plugins/hook_class_methods.rb +4 -0
  41. data/lib/sequel/plugins/many_through_many.rb +1 -1
  42. data/lib/sequel/plugins/nested_attributes.rb +40 -11
  43. data/lib/sequel/plugins/serialization.rb +17 -3
  44. data/lib/sequel/plugins/validation_helpers.rb +65 -18
  45. data/lib/sequel/sql.rb +23 -1
  46. data/lib/sequel/version.rb +1 -1
  47. data/spec/adapters/mssql_spec.rb +96 -10
  48. data/spec/adapters/mysql_spec.rb +19 -0
  49. data/spec/adapters/postgres_spec.rb +65 -2
  50. data/spec/adapters/sqlite_spec.rb +10 -0
  51. data/spec/core/core_sql_spec.rb +9 -0
  52. data/spec/core/database_spec.rb +8 -4
  53. data/spec/core/dataset_spec.rb +122 -29
  54. data/spec/core/expression_filters_spec.rb +17 -0
  55. data/spec/extensions/caching_spec.rb +43 -3
  56. data/spec/extensions/force_encoding_spec.rb +43 -1
  57. data/spec/extensions/nested_attributes_spec.rb +55 -2
  58. data/spec/extensions/validation_helpers_spec.rb +71 -0
  59. data/spec/integration/associations_test.rb +281 -0
  60. data/spec/integration/dataset_test.rb +383 -9
  61. data/spec/integration/eager_loader_test.rb +0 -65
  62. data/spec/integration/model_test.rb +110 -0
  63. data/spec/integration/plugin_test.rb +306 -0
  64. data/spec/integration/prepared_statement_test.rb +32 -0
  65. data/spec/integration/schema_test.rb +8 -3
  66. data/spec/integration/spec_helper.rb +1 -25
  67. data/spec/model/association_reflection_spec.rb +38 -0
  68. data/spec/model/associations_spec.rb +184 -8
  69. data/spec/model/eager_loading_spec.rb +23 -0
  70. data/spec/model/model_spec.rb +8 -0
  71. data/spec/model/record_spec.rb +84 -1
  72. 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 #{model} does not have a primary key") if opts.dataset_need_primary_key? && !pk
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[:keys].all?{|k| send(k)}
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
- raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
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.model} does not have a primary key") unless o.pk
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]].push(o) if associations.include?(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 #{model} does not have a primary key") unless pk
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
- raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
995
- raise(Sequel::Error, "associated object #{o.model} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
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 record: one of the #{callback_type} hooks returned false") if raise_error
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, "model object #{model} does not have a primary key") if o && !o.pk
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=>model.table_name, :alias_association_type_map=>{}, :alias_association_name_map=>{}, :reciprocals=>{}, :cartesian_product_number=>0})
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
@@ -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.opts[:from].first
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
- @values = values
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
- new? || !changed_columns.empty?
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 (which runs any callback methods).
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
- @values = dataset.first || raise(Error, "Record not found")
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:
@@ -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 initialize
10
- super{|h,k| h[k] = []}
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 include?(att)
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. You can add caching for any model
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 # 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
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 cache_store and cache_ttl to the subclass.
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(key)
56
- @cache_store.delete(key)
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
- if obj = @cache_store.get(ck)
69
- return obj
70
- end
71
- if obj = super(pk)
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