viking-sequel 3.10.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.
Files changed (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,41 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel by default does not use proxies for associations. The association
4
+ # method for *_to_many associations returns an array, and the association_dataset
5
+ # method returns a dataset. This plugin makes the association method return a proxy
6
+ # that will load the association and call a method on the association array if sent
7
+ # an array method, and otherwise send the method to the association's dataset.
8
+ module AssociationProxies
9
+ # A proxy for the association. Calling an array method will load the
10
+ # associated objects and call the method on the associated object array.
11
+ # Calling any other method will call that method on the association's dataset.
12
+ class AssociationProxy < BasicObject
13
+ # Empty array used to check if an array responds to the given method.
14
+ ARRAY = []
15
+
16
+ # Set the association reflection to use, and whether the association should be
17
+ # reloaded if an array method is called.
18
+ def initialize(instance, reflection, reload=nil)
19
+ @instance = instance
20
+ @reflection = reflection
21
+ @reload = reload
22
+ end
23
+
24
+ # Call the method given on the array of associated objects if the method
25
+ # is an array method, otherwise call the method on the association's dataset.
26
+ def method_missing(meth, *args, &block)
27
+ (ARRAY.respond_to?(meth) ? @instance.send(:load_associated_objects, @reflection, @reload) : @instance.send(@reflection.dataset_method)).
28
+ send(meth, *args, &block)
29
+ end
30
+ end
31
+
32
+ module ClassMethods
33
+ # Changes the association method to return a proxy instead of the associated objects
34
+ # directly.
35
+ def def_association_method(opts)
36
+ opts.returns_array? ? association_module_def(opts.association_method){|*r| AssociationProxy.new(self, opts, r[0])} : super
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,53 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The BooleaReaders plugin allows for the creation of attribute? methods
4
+ # for boolean columns, which provide a nicer API. By default, the accessors
5
+ # are created for all columns of type :boolean. However, you can provide a
6
+ # block to the plugin to change the criteria used to determine if a
7
+ # column is boolean:
8
+ #
9
+ # Sequel::Model.plugin(:boolean_readers){|c| db_schema[c][:db_type] =~ /\Atinyint/}
10
+ #
11
+ # This may be useful if you are using MySQL and have some tinyint columns
12
+ # that represent booleans and others that represent integers. You can turn
13
+ # the convert_tinyint_to_bool setting off and use the attribute methods for
14
+ # the integer value and the attribute? methods for the boolean value.
15
+ module BooleanReaders
16
+ # Default proc for determining if given column is a boolean, which
17
+ # just checks that the :type is boolean.
18
+ DEFAULT_BOOLEAN_ATTRIBUTE_PROC = lambda{|c| s = db_schema[c] and s[:type] == :boolean}
19
+
20
+ # Add the boolean_attribute? class method to the model, and create
21
+ # attribute? boolean reader methods for the class's columns if the class has a dataset.
22
+ def self.configure(model, &block)
23
+ model.meta_def(:boolean_attribute?, &(block || DEFAULT_BOOLEAN_ATTRIBUTE_PROC))
24
+ model.instance_eval{send(:create_boolean_readers) if @dataset}
25
+ end
26
+
27
+ module ClassMethods
28
+ # Create boolean readers for the class using the columns from the new dataset.
29
+ def set_dataset(*args)
30
+ super
31
+ create_boolean_readers
32
+ self
33
+ end
34
+
35
+ private
36
+
37
+ # Add a attribute? method for the column to a module included in the class.
38
+ def create_boolean_reader(column)
39
+ overridable_methods_module.module_eval do
40
+ define_method("#{column}?"){model.db.typecast_value(:boolean, send(column))}
41
+ end
42
+ end
43
+
44
+ # Add attribute? methods for all of the boolean attributes for this model.
45
+ def create_boolean_readers
46
+ im = instance_methods.collect{|x| x.to_s}
47
+ cs = columns rescue return
48
+ cs.each{|c| create_boolean_reader(c) if boolean_attribute?(c) && !im.include?("#{c}?")}
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,141 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel's built-in caching plugin supports caching to any object that
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
+ #
7
+ # Model.plugin :caching, store # Cache all models
8
+ # MyModel.plugin :caching, store # Just cache MyModel
9
+ #
10
+ # The cache store should implement the Ruby-Memcache API:
11
+ #
12
+ # cache_store.set(key, obj, time) # Associate the obj with the given key
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.
18
+ # cache_store.delete(key) # Remove key from cache
19
+ #
20
+ # If the :ignore_exceptions option is true, exceptions raised by cache_store.get
21
+ # are ignored and nil is returned instead. The memcached API is to
22
+ # raise an exception for a missing record, so if you use memcached, you will
23
+ # want to use this option.
24
+ #
25
+ # Note that only Model.[] method calls with a primary key argument are cached
26
+ # using this plugin.
27
+ module Caching
28
+ # Set the cache_store and cache_ttl attributes for the given model.
29
+ # If the :ttl option is not given, 3600 seconds is the default.
30
+ def self.configure(model, store, opts={})
31
+ model.instance_eval do
32
+ @cache_store = store
33
+ @cache_ttl = opts[:ttl] || 3600
34
+ @cache_ignore_exceptions = opts[:ignore_exceptions]
35
+ end
36
+ end
37
+
38
+ module ClassMethods
39
+ # If true, ignores exceptions when gettings cached records (the memcached API).
40
+ attr_reader :cache_ignore_exceptions
41
+
42
+ # The cache store object for the model, which should implement the
43
+ # Ruby-Memcache (or memcached) API
44
+ attr_reader :cache_store
45
+
46
+ # The time to live for the cache store, in seconds.
47
+ attr_reader :cache_ttl
48
+
49
+ # Set the time to live for the cache store, in seconds (default is 3600, # so 1 hour).
50
+ def set_cache_ttl(ttl)
51
+ @cache_ttl = ttl
52
+ end
53
+
54
+ # Copy the necessary class instance variables to the subclass.
55
+ def inherited(subclass)
56
+ super
57
+ store = @cache_store
58
+ ttl = @cache_ttl
59
+ cache_ignore_exceptions = @cache_ignore_exceptions
60
+ subclass.instance_eval do
61
+ @cache_store = store
62
+ @cache_ttl = ttl
63
+ @cache_ignore_exceptions = cache_ignore_exceptions
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ # Delete the entry with the matching key from the cache
70
+ def cache_delete(ck)
71
+ @cache_store.delete(ck)
72
+ nil
73
+ end
74
+
75
+ def cache_get(ck)
76
+ if @cache_ignore_exceptions
77
+ @cache_store.get(ck) rescue nil
78
+ else
79
+ @cache_store.get(ck)
80
+ end
81
+ end
82
+
83
+ # Return a key string for the pk
84
+ def cache_key(pk)
85
+ "#{self}:#{Array(pk).join(',')}"
86
+ end
87
+
88
+ # Set the object in the cache_store with the given key for cache_ttl seconds.
89
+ def cache_set(ck, obj)
90
+ @cache_store.set(ck, obj, @cache_ttl)
91
+ end
92
+
93
+ # Check the cache before a database lookup unless a hash is supplied.
94
+ def primary_key_lookup(pk)
95
+ ck = cache_key(pk)
96
+ unless obj = cache_get(ck)
97
+ if obj = super(pk)
98
+ cache_set(ck, obj)
99
+ end
100
+ end
101
+ obj
102
+ end
103
+ end
104
+
105
+ module InstanceMethods
106
+ # Remove the object from the cache when updating
107
+ def before_update
108
+ return false if super == false
109
+ cache_delete
110
+ end
111
+
112
+ # Return a key unique to the underlying record for caching, based on the
113
+ # primary key value(s) for the object. If the model does not have a primary
114
+ # key, raise an Error.
115
+ def cache_key
116
+ raise(Error, "No primary key is associated with this model") unless key = primary_key
117
+ pk = case key
118
+ when Array
119
+ key.collect{|k| @values[k]}
120
+ else
121
+ @values[key] || (raise Error, 'no primary key for this record')
122
+ end
123
+ model.send(:cache_key, pk)
124
+ end
125
+
126
+ # Remove the object from the cache when deleting
127
+ def delete
128
+ cache_delete
129
+ super
130
+ end
131
+
132
+ private
133
+
134
+ # Delete this object from the cache
135
+ def cache_delete
136
+ model.send(:cache_delete, cache_key)
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,214 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The class_table_inheritance plugin allows you to model inheritance in the
4
+ # database using a table per model class in the hierarchy, with only columns
5
+ # unique to that model class (or subclass hierarchy) being stored in the related
6
+ # table. For example, with this hierarchy:
7
+ #
8
+ # Employee
9
+ # / \
10
+ # Staff Manager
11
+ # |
12
+ # Executive
13
+ #
14
+ # the following database schema may be used (table - columns):
15
+ #
16
+ # * employees - id, name, kind
17
+ # * staff - id, manager_id
18
+ # * managers - id, num_staff
19
+ # * executives - id, num_managers
20
+ #
21
+ # The class_table_inheritance plugin assumes that the main table
22
+ # (e.g. employees) has a primary key field (usually autoincrementing),
23
+ # and all other tables have a foreign key of the same name that points
24
+ # to the same key in their superclass's table. For example:
25
+ #
26
+ # * employees.id - primary key, autoincrementing
27
+ # * staff.id - foreign key referencing employees(id)
28
+ # * managers.id - foreign key referencing employees(id)
29
+ # * executives.id - foreign key referencing managers(id)
30
+ #
31
+ # When using the class_table_inheritance plugin, subclasses use joined
32
+ # datasets:
33
+ #
34
+ # Employee.dataset.sql # SELECT * FROM employees
35
+ # Manager.dataset.sql # SELECT * FROM employees
36
+ # # INNER JOIN managers USING (id)
37
+ # Executive.dataset.sql # SELECT * FROM employees
38
+ # # INNER JOIN managers USING (id)
39
+ # # INNER JOIN executives USING (id)
40
+ #
41
+ # This allows Executive.all to return instances with all attributes
42
+ # loaded. The plugin overrides the deleting, inserting, and updating
43
+ # in the model to work with multiple tables, by handling each table
44
+ # individually.
45
+ #
46
+ # This plugin allows the use of a :key option when loading to mark
47
+ # a column holding a class name. This allows methods on the
48
+ # superclass to return instances of specific subclasses.
49
+ # This plugin also requires the lazy_attributes plugin and uses it to
50
+ # return subclass specific attributes that would not be loaded
51
+ # when calling superclass methods (since those wouldn't join
52
+ # to the subclass tables). For example:
53
+ #
54
+ # a = Employee.all # [<#Staff>, <#Manager>, <#Executive>]
55
+ # a.first.values # {:id=>1, name=>'S', :kind=>'Staff'}
56
+ # a.first.manager_id # Loads the manager_id attribute from the database
57
+ module ClassTableInheritance
58
+ # The class_table_inheritance plugin requires the lazy_attributes plugin
59
+ # to handle lazily-loaded attributes for subclass instances returned
60
+ # by superclass methods.
61
+ def self.apply(model, opts={}, &block)
62
+ model.plugin :lazy_attributes
63
+ end
64
+
65
+ # Initialize the per-model data structures and set the dataset's row_proc
66
+ # to check for the :key option column for the type of class when loading objects.
67
+ # Options:
68
+ # * :key - The column symbol holding the name of the model class this
69
+ # is an instance of. Necessary if you want to call model methods
70
+ # using the superclass, but have them return subclass instances.
71
+ # * :table_map - Hash with class name symbol keys and table name symbol
72
+ # values. Necessary if the implicit table name for the model class
73
+ # does not match the database table name
74
+ # Example:
75
+ # class Employee < Sequel::Model
76
+ # plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
77
+ # end
78
+ def self.configure(model, opts={}, &block)
79
+ model.instance_eval do
80
+ m = method(:constantize)
81
+ @cti_base_model = self
82
+ @cti_key = key = opts[:key]
83
+ @cti_tables = [table_name]
84
+ @cti_columns = {table_name=>columns}
85
+ @cti_table_map = opts[:table_map] || {}
86
+ dataset.row_proc = lambda{|r| (m.call(r[key]) rescue model).load(r)}
87
+ end
88
+ end
89
+
90
+ module ClassMethods
91
+ # The parent/root/base model for this class table inheritance hierarchy.
92
+ # This is the only model in the hierarchy that load the
93
+ # class_table_inheritance plugin.
94
+ attr_reader :cti_base_model
95
+
96
+ # Hash with table name symbol keys and arrays of column symbol values,
97
+ # giving the columns to update in each backing database table.
98
+ attr_reader :cti_columns
99
+
100
+ # The column containing the class name as a string. Used to
101
+ # return instances of subclasses when calling the superclass's
102
+ # load method.
103
+ attr_reader :cti_key
104
+
105
+ # An array of table symbols that back this model. The first is
106
+ # cti_base_model table symbol, and the last is the current model
107
+ # table symbol.
108
+ attr_reader :cti_tables
109
+
110
+ # A hash with class name symbol keys and table name symbol values.
111
+ # Specified with the :table_map option to the plugin, and used if
112
+ # the implicit naming is incorrect.
113
+ attr_reader :cti_table_map
114
+
115
+ # Add the appropriate data structures to the subclass. Does not
116
+ # allow anonymous subclasses to be created, since they would not
117
+ # be mappable to a table.
118
+ def inherited(subclass)
119
+ cc = cti_columns
120
+ ck = cti_key
121
+ ct = cti_tables.dup
122
+ ctm = cti_table_map.dup
123
+ cbm = cti_base_model
124
+ pk = primary_key
125
+ ds = dataset
126
+ subclass.instance_eval do
127
+ raise(Error, "cannot create anonymous subclass for model class using class_table_inheritance") if !(n = name) || n.empty?
128
+ table = ctm[n.to_sym] || implicit_table_name
129
+ columns = db.from(table).columns
130
+ @cti_key = ck
131
+ @cti_tables = ct + [table]
132
+ @cti_columns = cc.merge(table=>columns)
133
+ @cti_table_map = ctm
134
+ @cti_base_model = cbm
135
+ # Need to set dataset and columns before calling super so that
136
+ # the main column accessor module is included in the class before any
137
+ # plugin accessor modules (such as the lazy attributes accessor module).
138
+ set_dataset(ds.join(table, [pk]))
139
+ set_columns(self.columns)
140
+ end
141
+ super
142
+ subclass.instance_eval do
143
+ m = method(:constantize)
144
+ dataset.row_proc = lambda{|r| (m.call(r[ck]) rescue subclass).load(r)}
145
+ (columns - [cbm.primary_key]).each{|a| define_lazy_attribute_getter(a)}
146
+ cti_tables.reverse.each do |table|
147
+ db.schema(table).each{|k,v| db_schema[k] = v}
148
+ end
149
+ end
150
+ end
151
+
152
+ # The primary key in the parent/base/root model, which should have a
153
+ # foreign key with the same name referencing it in each model subclass.
154
+ def primary_key
155
+ return super if self == cti_base_model
156
+ cti_base_model.primary_key
157
+ end
158
+
159
+ # The table name for the current model class's main table (not used
160
+ # by any superclasses).
161
+ def table_name
162
+ self == cti_base_model ? super : cti_tables.last
163
+ end
164
+ end
165
+
166
+ module InstanceMethods
167
+ # Set the cti_key column to the name of the model.
168
+ def before_create
169
+ return false if super == false
170
+ send("#{model.cti_key}=", model.name.to_s) if model.cti_key
171
+ end
172
+
173
+ # Delete the row from all backing tables, starting from the
174
+ # most recent table and going through all superclasses.
175
+ def delete
176
+ m = model
177
+ m.cti_tables.reverse.each do |table|
178
+ m.db.from(table).filter(m.primary_key=>pk).delete
179
+ end
180
+ self
181
+ end
182
+
183
+ private
184
+
185
+ # Insert rows into all backing tables, using the columns
186
+ # in each table.
187
+ def _insert
188
+ return super if model == model.cti_base_model
189
+ iid = nil
190
+ m = model
191
+ m.cti_tables.each do |table|
192
+ h = {}
193
+ h[m.primary_key] = iid if iid
194
+ m.cti_columns[table].each{|c| h[c] = @values[c] if @values.include?(c)}
195
+ nid = m.db.from(table).insert(h)
196
+ iid ||= nid
197
+ end
198
+ @values[primary_key] = iid
199
+ end
200
+
201
+ # Update rows in all backing tables, using the columns in each table.
202
+ def _update(columns)
203
+ pkh = pk_hash
204
+ m = model
205
+ m.cti_tables.each do |table|
206
+ h = {}
207
+ m.cti_columns[table].each{|c| h[c] = columns[c] if columns.include?(c)}
208
+ m.db.from(table).filter(pkh).update(h) unless h.empty?
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end