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,138 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The composition plugin allows you to easily define getter and
4
+ # setter instance methods for a class where the backing data
5
+ # is composed of other getters and decomposed to other setters.
6
+ #
7
+ # A simple example of this is when you have a database table with
8
+ # separate columns for year, month, and day, but where you want
9
+ # to deal with Date objects in your ruby code. This can be handled
10
+ # with:
11
+ #
12
+ # Model.composition :date, :mapping=>[:year, :month, :day]
13
+ #
14
+ # The :mapping option is optional, but you can define custom
15
+ # composition and decomposition procs via the :composer and
16
+ # :decomposer options.
17
+ #
18
+ # Note that when using the composition object, you should not
19
+ # modify the underlying columns if you are also instantiating
20
+ # the composition, as otherwise the composition object values
21
+ # will override any underlying columns when the object is saved.
22
+ module Composition
23
+ # Define the necessary class instance variables.
24
+ def self.apply(model)
25
+ model.instance_eval{@compositions = {}}
26
+ end
27
+
28
+ module ClassMethods
29
+ # A hash with composition name keys and composition reflection
30
+ # hash values.
31
+ attr_reader :compositions
32
+
33
+ # A module included in the class holding the composition
34
+ # getter and setter methods.
35
+ attr_reader :composition_module
36
+
37
+ # Define a composition for this model, with name being the name of the composition.
38
+ # You must provide either a :mapping option or both the :composer and :decomposer options.
39
+ #
40
+ # Options:
41
+ # * :class - if using the :mapping option, the class to use, as a Class, String or Symbol.
42
+ # * :composer - A proc that is instance evaled when the composition getter method is called
43
+ # to create the composition.
44
+ # * :decomposer - A proc that is instance evaled before saving the model object,
45
+ # if the composition object exists, which sets the columns in the model object
46
+ # based on the value of the composition object.
47
+ # * :mapping - An array where each element is either a symbol or an array of two symbols.
48
+ # A symbol is treated like an array of two symbols where both symbols are the same.
49
+ # The first symbol represents the getter method in the model, and the second symbol
50
+ # represents the getter method in the composition object. Example:
51
+ # # Uses columns year, month, and day in the current model
52
+ # # Uses year, month, and day methods in the composition object
53
+ # :mapping=>[:year, :month, :day]
54
+ # # Uses columns year, month, and day in the current model
55
+ # # Uses y, m, and d methods in the composition object where
56
+ # # for example y in the composition object represents year
57
+ # # in the model object.
58
+ # :mapping=>[[:year, :y], [:month, :m], [:day, :d]]
59
+ def composition(name, opts={})
60
+ opts = opts.dup
61
+ compositions[name] = opts
62
+ if mapping = opts[:mapping]
63
+ keys = mapping.map{|k| k.is_a?(Array) ? k.first : k}
64
+ if !opts[:composer]
65
+ late_binding_class_option(opts, name)
66
+ klass = opts[:class]
67
+ class_proc = proc{klass || constantize(opts[:class_name])}
68
+ opts[:composer] = proc do
69
+ if values = keys.map{|k| send(k)} and values.any?{|v| !v.nil?}
70
+ class_proc.call.new(*values)
71
+ else
72
+ nil
73
+ end
74
+ end
75
+ end
76
+ if !opts[:decomposer]
77
+ setter_meths = keys.map{|k| :"#{k}="}
78
+ cov_methods = mapping.map{|k| k.is_a?(Array) ? k.last : k}
79
+ setters = setter_meths.zip(cov_methods)
80
+ opts[:decomposer] = proc do
81
+ if (o = compositions[name]).nil?
82
+ setter_meths.each{|sm| send(sm, nil)}
83
+ else
84
+ setters.each{|sm, cm| send(sm, o.send(cm))}
85
+ end
86
+ end
87
+ end
88
+ end
89
+ raise(Error, "Must provide :composer and :decomposer options, or :mapping option") unless opts[:composer] && opts[:decomposer]
90
+ define_composition_accessor(name, opts)
91
+ end
92
+
93
+ # Copy the necessary class instance variables to the subclass.
94
+ def inherited(subclass)
95
+ super
96
+ c = compositions.dup
97
+ subclass.instance_eval{@compositions = c}
98
+ end
99
+
100
+ # Define getter and setter methods for the composition object.
101
+ def define_composition_accessor(name, opts={})
102
+ include(@composition_module ||= Module.new) unless composition_module
103
+ composer = opts[:composer]
104
+ composition_module.class_eval do
105
+ define_method(name) do
106
+ compositions.include?(name) ? compositions[name] : (compositions[name] = instance_eval(&composer))
107
+ end
108
+ define_method("#{name}=") do |v|
109
+ modified!
110
+ compositions[name] = v
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ module InstanceMethods
117
+ # Clear the cached compositions when refreshing.
118
+ def _refresh(ds)
119
+ v = super
120
+ compositions.clear
121
+ v
122
+ end
123
+
124
+ # For each composition, set the columns in the model class based
125
+ # on the composition object.
126
+ def before_save
127
+ @compositions.keys.each{|n| instance_eval(&model.compositions[n][:decomposer])} if @compositions
128
+ super
129
+ end
130
+
131
+ # Cache of composition objects for this class.
132
+ def compositions
133
+ @compositions ||= {}
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,72 @@
1
+ if RUBY_VERSION >= '1.9.0'
2
+ module Sequel
3
+ module Plugins
4
+ # The ForceEncoding plugin allows you force specific encodings for all
5
+ # strings that are used by the model. When model instances are loaded
6
+ # from the database, all values in the hash that are strings are
7
+ # forced to the given encoding. Whenever you update a model column
8
+ # attribute, the resulting value is forced to a given encoding if the
9
+ # value is a string. There are two ways to specify the encoding. You
10
+ # can either do so in the plugin call itself, or via the
11
+ # forced_encoding class accessor:
12
+ #
13
+ # class Album < Sequel::Model
14
+ # plugin :force_encoding, 'UTF-8'
15
+ # # or
16
+ # plugin :force_encoding
17
+ # self.forced_encoding = 'UTF-8'
18
+ # end
19
+ module ForceEncoding
20
+ # Set the forced_encoding based on the value given in the plugin call.
21
+ # Note that if a the plugin has been previously loaded, any previous
22
+ # forced encoding is overruled, even if no encoding is given when calling
23
+ # the plugin.
24
+ def self.configure(model, encoding=nil)
25
+ model.forced_encoding = encoding
26
+ end
27
+
28
+ module ClassMethods
29
+ # The string encoding to force on a column string values
30
+ attr_accessor :forced_encoding
31
+
32
+ # Copy the forced_encoding value into the subclass
33
+ def inherited(subclass)
34
+ super
35
+ subclass.forced_encoding = forced_encoding
36
+ end
37
+ end
38
+
39
+ module InstanceMethods
40
+ # Allow the force encoding plugin to work with the identity_map
41
+ # plugin by typecasting new values.
42
+ def merge_db_update(row)
43
+ super(force_hash_encoding(row))
44
+ end
45
+
46
+ private
47
+
48
+ # Force the encoding for all string values in the given row hash.
49
+ def force_hash_encoding(row)
50
+ fe = model.forced_encoding
51
+ row.values.each{|v| v.force_encoding(fe) if v.is_a?(String)} if fe
52
+ row
53
+ end
54
+
55
+ # Force the encoding of all string values when setting the instance's values.
56
+ def set_values(row)
57
+ super(force_hash_encoding(row))
58
+ end
59
+
60
+ # Force the encoding of all returned strings to the model's forced_encoding.
61
+ def typecast_value(column, value)
62
+ s = super
63
+ s.force_encoding(model.forced_encoding) if s.is_a?(String) && model.forced_encoding
64
+ s
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ else
71
+ raise LoadError, 'ForceEncoding plugin only works on Ruby 1.9+'
72
+ end
@@ -0,0 +1,126 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel's built-in hook class methods plugin is designed for backwards
4
+ # compatibility. Its use is not encouraged, it is recommended to use
5
+ # instance methods and super instead of this plugin. What this plugin
6
+ # allows you to do is, for example:
7
+ #
8
+ # # Block only, can cause duplicate hooks if code is reloaded
9
+ # before_save{self.created_at = Time.now}
10
+ # # Block with tag, safe for reloading
11
+ # before_save(:set_created_at){self.created_at = Time.now}
12
+ # # Tag only, safe for reloading, calls instance method
13
+ # before_save(:set_created_at)
14
+ #
15
+ # Pretty much anything you can do with a hook class method, you can also
16
+ # do with an instance method instead:
17
+ #
18
+ # def before_save
19
+ # return false if super == false
20
+ # self.created_at = Time.now
21
+ # end
22
+ #
23
+ # Note that returning false in any before hook block will skip further
24
+ # before hooks and abort the action. So if a before_save hook block returns
25
+ # false, future before_save hook blocks are not called, and the save is aborted.
26
+ module HookClassMethods
27
+ # Set up the hooks instance variable in the model.
28
+ def self.apply(model)
29
+ hooks = model.instance_variable_set(:@hooks, {})
30
+ Model::HOOKS.each{|h| hooks[h] = []}
31
+ end
32
+
33
+ module ClassMethods
34
+ Model::HOOKS.each{|h| class_eval("def #{h}(method = nil, &block); add_hook(:#{h}, method, &block) end", __FILE__, __LINE__)}
35
+
36
+ # This adds a new hook type. It will define both a class
37
+ # method that you can use to add hooks, as well as an instance method
38
+ # that you can use to call all hooks of that type. The class method
39
+ # can be called with a symbol or a block or both. If a block is given and
40
+ # and symbol is not, it adds the hook block to the hook type. If a block
41
+ # and symbol are both given, it replaces the hook block associated with
42
+ # that symbol for a given hook type, or adds it if there is no hook block
43
+ # with that symbol for that hook type. If no block is given, it assumes
44
+ # the symbol specifies an instance method to call and adds it to the hook
45
+ # type.
46
+ #
47
+ # If any hook block returns false, the instance method will return false
48
+ # immediately without running the rest of the hooks of that type.
49
+ #
50
+ # It is recommended that you always provide a symbol to this method,
51
+ # for descriptive purposes. It's only necessary to do so when you
52
+ # are using a system that reloads code.
53
+ #
54
+ # Example of usage:
55
+ #
56
+ # class MyModel
57
+ # define_hook :before_move_to
58
+ # before_move_to(:check_move_allowed){|o| o.allow_move?}
59
+ # def move_to(there)
60
+ # return if before_move_to == false
61
+ # # move MyModel object to there
62
+ # end
63
+ # end
64
+ def add_hook_type(*hooks)
65
+ Model::HOOKS.concat(hooks)
66
+ hooks.each do |hook|
67
+ @hooks[hook] = []
68
+ instance_eval("def #{hook}(method = nil, &block); add_hook(:#{hook}, method, &block) end", __FILE__, __LINE__)
69
+ class_eval("def #{hook}; run_hooks(:#{hook}); end", __FILE__, __LINE__)
70
+ end
71
+ end
72
+
73
+ # Returns true if there are any hook blocks for the given hook.
74
+ def has_hooks?(hook)
75
+ !@hooks[hook].empty?
76
+ end
77
+
78
+ # Yield every block related to the given hook.
79
+ def hook_blocks(hook)
80
+ @hooks[hook].each{|k,v| yield v}
81
+ end
82
+
83
+ # Make a copy of the current class's hooks for the subclass.
84
+ def inherited(subclass)
85
+ super
86
+ hooks = subclass.instance_variable_set(:@hooks, {})
87
+ instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup}
88
+ end
89
+
90
+ private
91
+
92
+ # Add a hook block to the list of hook methods.
93
+ # If a non-nil tag is given and it already is in the list of hooks,
94
+ # replace it with the new block.
95
+ def add_hook(hook, tag, &block)
96
+ unless block
97
+ (raise Error, 'No hook method specified') unless tag
98
+ block = proc {send tag}
99
+ end
100
+ h = @hooks[hook]
101
+ if tag && (old = h.find{|x| x[0] == tag})
102
+ old[1] = block
103
+ else
104
+ if hook.to_s =~ /^before/
105
+ h.unshift([tag,block])
106
+ else
107
+ h << [tag, block]
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ module InstanceMethods
114
+ Model::HOOKS.each{|h| class_eval("def #{h}; return false if super == false; run_hooks(:#{h}); end", __FILE__, __LINE__)}
115
+
116
+ private
117
+
118
+ # Runs all hook blocks of given hook type on this object.
119
+ # Stops running hook blocks and returns false if any hook block returns false.
120
+ def run_hooks(hook)
121
+ model.hook_blocks(hook){|block| return false if instance_eval(&block) == false}
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,116 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The identity_map plugin allows the user to create temporary identity maps
4
+ # via the with_identity_map method, which takes a block. Inside the block,
5
+ # objects have a 1-1 correspondence with rows in the database.
6
+ #
7
+ # For example, the following is true, and wouldn't be true if you weren't
8
+ # using the identity map:
9
+ # Sequel::Model.with_identity_map do
10
+ # Album.filter{(id > 0) & (id < 2)}.first.object_id == Album.first(:id=>1).object_id
11
+ # end
12
+ #
13
+ # In additional to providing a 1-1 correspondence, the identity_map plugin
14
+ # also provides a cached looked up of records in two cases:
15
+ # * Model.[] (e.g. Album[1])
16
+ # * Model.many_to_one accessor methods (e.g. album.artist)
17
+ #
18
+ # If the object you are looking up using one of those two methods is already
19
+ # in the identity map, the record is returned without a database query being
20
+ # issued.
21
+ #
22
+ # Identity maps are thread-local and only presist for the duration of the block,
23
+ # so they should be should only be considered as a possible performance enhancer.
24
+ module IdentityMap
25
+ module ClassMethods
26
+ # Returns the current thread-local identity map. Should be a hash if
27
+ # there is an active identity map, and nil otherwise.
28
+ def identity_map
29
+ Thread.current[:sequel_identity_map]
30
+ end
31
+
32
+ # The identity map key for an object of the current class with the given pk.
33
+ # May not always be correct for a class which uses STI.
34
+ def identity_map_key(pk)
35
+ "#{self}:#{pk ? Array(pk).join(',') : "nil:#{rand}"}"
36
+ end
37
+
38
+ # If the identity map is in use, check it for a current copy of the object.
39
+ # If a copy does not exist, create a new object and add it to the identity map.
40
+ # If a copy exists, add any values in the given row that aren't currently
41
+ # in the object to the object's values. This allows you to only request
42
+ # certain fields in an initial query, make modifications to some of those
43
+ # fields and request other, potentially overlapping fields in a new query,
44
+ # and not have the second query override fields you modified.
45
+ def load(row)
46
+ return super unless idm = identity_map
47
+ if o = idm[identity_map_key(Array(primary_key).map{|x| row[x]})]
48
+ o.merge_db_update(row)
49
+ else
50
+ o = super
51
+ idm[identity_map_key(o.pk)] = o
52
+ end
53
+ o
54
+ end
55
+
56
+ # Take a block and inside that block use an identity map to ensure a 1-1
57
+ # correspondence of objects to the database row they represent.
58
+ def with_identity_map
59
+ return yield if identity_map
60
+ begin
61
+ self.identity_map = {}
62
+ yield
63
+ ensure
64
+ self.identity_map = nil
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ # Set the thread local identity map to the given value.
71
+ def identity_map=(v)
72
+ Thread.current[:sequel_identity_map] = v
73
+ end
74
+
75
+ # Check the current identity map if it exists for the object with
76
+ # the matching pk. If one is found, return it, otherwise call super.
77
+ def primary_key_lookup(pk)
78
+ (idm = identity_map and o = idm[identity_map_key(pk)]) ? o : super
79
+ end
80
+ end
81
+
82
+ module InstanceMethods
83
+ # Remove instances from the identity map cache if they are deleted.
84
+ def delete
85
+ super
86
+ if idm = model.identity_map
87
+ idm.delete(model.identity_map_key(pk))
88
+ end
89
+ self
90
+ end
91
+
92
+ # Merge the current values into the values provided in the row, ensuring
93
+ # that current values are not overridden by new values.
94
+ def merge_db_update(row)
95
+ @values = row.merge(@values)
96
+ end
97
+
98
+ private
99
+
100
+ # If the association is a many_to_one and it has a :key option and the
101
+ # key option has a value and the association uses the primary key of
102
+ # the associated class as the :primary_key option, check the identity
103
+ # map for the associated object and return it if present.
104
+ def _load_associated_objects(opts)
105
+ klass = opts.associated_class
106
+ if idm = model.identity_map and opts[:type] == :many_to_one and opts[:primary_key] == klass.primary_key and
107
+ opts[:key] and pk = send(opts[:key]) and o = idm[klass.identity_map_key(pk)]
108
+ o
109
+ else
110
+ super
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end