viking-sequel 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
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