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,81 @@
1
+ module Sequel
2
+ require 'plugins/instance_filters'
3
+
4
+ module Plugins
5
+ # This plugin implements a simple database-independent locking mechanism
6
+ # to ensure that concurrent updates do not override changes. This is
7
+ # best implemented by a code example:
8
+ #
9
+ # class Person < Sequel::Model
10
+ # plugin :optimistic_locking
11
+ # end
12
+ # p1 = Person[1]
13
+ # p2 = Person[1]
14
+ # p1.update(:name=>'Jim') # works
15
+ # p2.update(:name=>'Bob') # raises Sequel::Plugins::OptimisticLocking::Error
16
+ #
17
+ # In order for this plugin to work, you need to make sure that the database
18
+ # table has a lock_version column (or other column you name via the lock_column
19
+ # class level accessor) that defaults to 0.
20
+ #
21
+ # This plugin relies on the instance_filters plugin.
22
+ module OptimisticLocking
23
+ # Exception class raised when trying to update or destroy a stale object.
24
+ Error = InstanceFilters::Error
25
+
26
+ # Load the instance_filters plugin into the model.
27
+ def self.apply(model, opts={})
28
+ model.plugin :instance_filters
29
+ end
30
+
31
+ # Set the lock_column to the :lock_column option, or :lock_version if
32
+ # that option is not given.
33
+ def self.configure(model, opts={})
34
+ model.lock_column = opts[:lock_column] || :lock_version
35
+ end
36
+
37
+ module ClassMethods
38
+ # The column holding the version of the lock
39
+ attr_accessor :lock_column
40
+
41
+ # Copy the lock_column value into the subclass
42
+ def inherited(subclass)
43
+ super
44
+ subclass.lock_column = lock_column
45
+ end
46
+ end
47
+
48
+ module InstanceMethods
49
+ # Add the lock column instance filter to the object before destroying it.
50
+ def before_destroy
51
+ lock_column_instance_filter
52
+ super
53
+ end
54
+
55
+ # Add the lock column instance filter to the object before updating it.
56
+ def before_update
57
+ lock_column_instance_filter
58
+ super
59
+ end
60
+
61
+ private
62
+
63
+ # Add the lock column instance filter to the object.
64
+ def lock_column_instance_filter
65
+ lc = model.lock_column
66
+ instance_filter(lc=>send(lc))
67
+ end
68
+
69
+ # Only update the row if it has the same lock version, and increment the
70
+ # lock version.
71
+ def _update(columns)
72
+ lc = model.lock_column
73
+ lcv = send(lc)
74
+ columns[lc] = lcv + 1
75
+ super
76
+ send("#{lc}=", lcv + 1)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,281 @@
1
+ module Sequel
2
+ module Plugins
3
+ # = Overview
4
+ #
5
+ # The rcte_tree plugin deals with tree structured data stored
6
+ # in the database using the adjacency list model (where child rows
7
+ # have a foreign key pointing to the parent rows), using recursive
8
+ # common table expressions to load all ancestors in a single query,
9
+ # all descendants in a single query, and all descendants to a given
10
+ # level (where level 1 is children, level 2 is children and grandchildren
11
+ # etc.) in a single query.
12
+ #
13
+ # = Background
14
+ #
15
+ # There are two types of common models for storing tree structured data
16
+ # in an SQL database, the adjacency list model and the nested set model.
17
+ # Before recursive common table expressions (or similar capabilities such
18
+ # as CONNECT BY for Oracle), the nested set model was the only easy way
19
+ # to retrieve all ancestors and descendants in a single query. However,
20
+ # it has significant performance corner cases.
21
+ #
22
+ # On PostgreSQL 8.4, with a significant number of rows, the nested set
23
+ # model is almost 500 times slower than using a recursive common table
24
+ # expression with the adjacency list model to get all descendants, and
25
+ # almost 24,000 times slower to get all descendants to a given level.
26
+ #
27
+ # Considering that the nested set model requires more difficult management
28
+ # than the adjacency list model, it's almost always better to use the
29
+ # adjacency list model if your database supports common table expressions.
30
+ # See http://explainextended.com/2009/09/24/adjacency-list-vs-nested-sets-postgresql/
31
+ # for detailed analysis.
32
+ #
33
+ # = Usage
34
+ #
35
+ # The rcte_tree plugin is unlike most plugins in that it doesn't add any class,
36
+ # instance, or dataset modules. It only has a single apply method, which
37
+ # adds four associations to the model: parent, children, ancestors, and
38
+ # descendants. Both the parent and children are fairly standard many_to_one
39
+ # and one_to_many associations, respectively. However, the ancestors and
40
+ # descendants associations are special. Both the ancestors and descendants
41
+ # associations will automatically set the parent and children associations,
42
+ # respectively, for current object and all of the ancestor or descendant
43
+ # objects, whenever they are loaded (either eagerly or lazily). Additionally,
44
+ # the descendants association can take a level argument when called eagerly,
45
+ # which limits the returned objects to only that many levels in the tree (see
46
+ # the Overview).
47
+ #
48
+ # Model.plugin :rcte_tree
49
+ #
50
+ # # Lazy loading
51
+ # model = Model.first
52
+ # model.parent
53
+ # model.children
54
+ # model.ancestors # Populates :parent association for all ancestors
55
+ # model.descendants # Populates :children association for all descendants
56
+ #
57
+ # # Eager loading - also populates the :parent and children associations
58
+ # # for all ancestors and descendants
59
+ # Model.filter(:id=>[1, 2]).eager(:ancestors, :descendants).all
60
+ #
61
+ # # Eager loading children and grand children
62
+ # Model.filter(:id=>[1, 2]).eager(:descendants=>2).all
63
+ # # Eager loading children, grand children, and great grand children
64
+ # Model.filter(:id=>[1, 2]).eager(:descendants=>3).all
65
+ #
66
+ # = Options
67
+ #
68
+ # You can override the options for any specific association by making
69
+ # sure the plugin options contain one of the following keys:
70
+ #
71
+ # * :parent - hash of options for the parent association
72
+ # * :children - hash of options for the children association
73
+ # * :ancestors - hash of options for the ancestors association
74
+ # * :descendants - hash of options for the descendants association
75
+ #
76
+ # Note that you can change the name of the above associations by specifying
77
+ # a :name key in the appropriate hash of options above. For example:
78
+ #
79
+ # Model.plugin :rcte_tree, :parent=>{:name=>:mother},
80
+ # :children=>{:name=>:daughters}, :descendants=>{:name=>:offspring}
81
+ #
82
+ # Any other keys in the main options hash are treated as options shared by
83
+ # all of the associations. Here's a few options that affect the plugin:
84
+ #
85
+ # * :key - The foreign key in the table that points to the primary key
86
+ # of the parent (default: :parent_id)
87
+ # * :primary_key - The primary key to use (default: the model's primary key)
88
+ # * :key_alias - The symbol identifier to use for aliasing when eager
89
+ # loading (default: :x_root_x)
90
+ # * :cte_name - The symbol identifier to use for the common table expression
91
+ # (default: :t)
92
+ # * :level_alias - The symbol identifier to use when eagerly loading descendants
93
+ # up to a given level (default: :x_level_x)
94
+ module RcteTree
95
+ # Create the appropriate parent, children, ancestors, and descendants
96
+ # associations for the model.
97
+ def self.apply(model, opts={})
98
+ opts = opts.dup
99
+ opts[:class] = model
100
+
101
+ key = opts[:key] ||= :parent_id
102
+ prkey = opts[:primary_key] ||= model.primary_key
103
+
104
+ par = opts.merge(opts.fetch(:parent, {}))
105
+ parent = par.fetch(:name, :parent)
106
+ model.many_to_one parent, par
107
+
108
+ chi = opts.merge(opts.fetch(:children, {}))
109
+ childrena = chi.fetch(:name, :children)
110
+ model.one_to_many childrena, chi
111
+
112
+ ka = opts[:key_alias] ||= :x_root_x
113
+ t = opts[:cte_name] ||= :t
114
+ opts[:reciprocal] = nil
115
+ c_all = SQL::ColumnAll.new(model.table_name)
116
+
117
+ a = opts.merge(opts.fetch(:ancestors, {}))
118
+ ancestors = a.fetch(:name, :ancestors)
119
+ a[:read_only] = true unless a.has_key?(:read_only)
120
+ a[:eager_loader_key] = key
121
+ a[:dataset] ||= proc do
122
+ model.from(t).
123
+ with_recursive(t, model.filter(prkey=>send(key)),
124
+ model.join(t, key=>prkey).
125
+ select(c_all))
126
+ end
127
+ aal = Array(a[:after_load])
128
+ aal << proc do |m, ancs|
129
+ unless m.associations.has_key?(parent)
130
+ parent_map = {m[prkey]=>m}
131
+ child_map = {}
132
+ child_map[m[key]] = m if m[key]
133
+ m.associations[parent] = nil
134
+ ancs.each do |obj|
135
+ obj.associations[parent] = nil
136
+ parent_map[obj[prkey]] = obj
137
+ if ok = obj[key]
138
+ child_map[ok] = obj
139
+ end
140
+ end
141
+ parent_map.each do |parent_id, obj|
142
+ if child = child_map[parent_id]
143
+ child.associations[parent] = obj
144
+ end
145
+ end
146
+ end
147
+ end
148
+ a[:after_load] ||= aal
149
+ a[:eager_loader] ||= proc do |key_hash, objects, associations|
150
+ id_map = key_hash[key]
151
+ parent_map = {}
152
+ children_map = {}
153
+ objects.each do |obj|
154
+ parent_map[obj[prkey]] = obj
155
+ (children_map[obj[key]] ||= []) << obj
156
+ obj.associations[ancestors] = []
157
+ obj.associations[parent] = nil
158
+ end
159
+ r = model.association_reflection(ancestors)
160
+ model.eager_loading_dataset(r,
161
+ model.from(t).
162
+ with_recursive(t, model.filter(prkey=>id_map.keys).
163
+ select(SQL::AliasedExpression.new(prkey, ka), c_all),
164
+ model.join(t, key=>prkey).
165
+ select(SQL::QualifiedIdentifier.new(t, ka), c_all)),
166
+ r.select,
167
+ associations).all do |obj|
168
+ opk = obj[prkey]
169
+ if in_pm = parent_map.has_key?(opk)
170
+ if idm_obj = parent_map[opk]
171
+ idm_obj.values[ka] = obj.values[ka]
172
+ obj = idm_obj
173
+ end
174
+ else
175
+ obj.associations[parent] = nil
176
+ parent_map[opk] = obj
177
+ (children_map[obj[key]] ||= []) << obj
178
+ end
179
+
180
+ if roots = id_map[obj.values.delete(ka)]
181
+ roots.each do |root|
182
+ root.associations[ancestors] << obj
183
+ end
184
+ end
185
+ end
186
+ parent_map.each do |parent_id, obj|
187
+ if children = children_map[parent_id]
188
+ children.each do |child|
189
+ child.associations[parent] = obj
190
+ end
191
+ end
192
+ end
193
+ end
194
+ model.one_to_many ancestors, a
195
+
196
+ d = opts.merge(opts.fetch(:descendants, {}))
197
+ descendants = d.fetch(:name, :descendants)
198
+ d[:read_only] = true unless d.has_key?(:read_only)
199
+ la = d[:level_alias] ||= :x_level_x
200
+ d[:dataset] ||= proc do
201
+ model.from(t).
202
+ with_recursive(t, model.filter(key=>send(prkey)),
203
+ model.join(t, prkey=>key).
204
+ select(SQL::ColumnAll.new(model.table_name)))
205
+ end
206
+ dal = Array(d[:after_load])
207
+ dal << proc do |m, descs|
208
+ unless m.associations.has_key?(childrena)
209
+ parent_map = {m[prkey]=>m}
210
+ children_map = {}
211
+ m.associations[childrena] = []
212
+ descs.each do |obj|
213
+ obj.associations[childrena] = []
214
+ if opk = obj[prkey]
215
+ parent_map[opk] = obj
216
+ end
217
+ if ok = obj[key]
218
+ (children_map[ok] ||= []) << obj
219
+ end
220
+ end
221
+ children_map.each do |parent_id, objs|
222
+ parent_map[parent_id].associations[childrena] = objs
223
+ end
224
+ end
225
+ end
226
+ d[:after_load] = dal
227
+ d[:eager_loader] ||= proc do |key_hash, objects, associations|
228
+ id_map = key_hash[prkey]
229
+ parent_map = {}
230
+ children_map = {}
231
+ objects.each do |obj|
232
+ parent_map[obj[prkey]] = obj
233
+ obj.associations[descendants] = []
234
+ obj.associations[childrena] = []
235
+ end
236
+ r = model.association_reflection(descendants)
237
+ base_case = model.filter(key=>id_map.keys).
238
+ select(SQL::AliasedExpression.new(key, ka), c_all)
239
+ recursive_case = model.join(t, prkey=>key).
240
+ select(SQL::QualifiedIdentifier.new(t, ka), c_all)
241
+ if associations.is_a?(Integer)
242
+ level = associations
243
+ no_cache_level = level - 1
244
+ associations = {}
245
+ base_case = base_case.select_more(SQL::AliasedExpression.new(0, la))
246
+ recursive_case = recursive_case.select_more(SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, la) + 1, la)).filter(SQL::QualifiedIdentifier.new(t, la) < level - 1)
247
+ end
248
+ model.eager_loading_dataset(r,
249
+ model.from(t).with_recursive(t, base_case, recursive_case),
250
+ r.select,
251
+ associations).all do |obj|
252
+ if level
253
+ no_cache = no_cache_level == obj.values.delete(la)
254
+ end
255
+
256
+ opk = obj[prkey]
257
+ if in_pm = parent_map.has_key?(opk)
258
+ if idm_obj = parent_map[opk]
259
+ idm_obj.values[ka] = obj.values[ka]
260
+ obj = idm_obj
261
+ end
262
+ else
263
+ obj.associations[childrena] = [] unless no_cache
264
+ parent_map[opk] = obj
265
+ end
266
+
267
+ if root = id_map[obj.values.delete(ka)].first
268
+ root.associations[descendants] << obj
269
+ end
270
+
271
+ (children_map[obj[key]] ||= []) << obj
272
+ end
273
+ children_map.each do |parent_id, objs|
274
+ parent_map[parent_id].associations[childrena] = objs.uniq
275
+ end
276
+ end
277
+ model.one_to_many descendants, d
278
+ end
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,66 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel's built in schema plugin allows you to define your schema
4
+ # directly in the model using Model.set_schema (which takes a block
5
+ # similar to Database#create_table), and use Model.create_table to
6
+ # create a table using the schema information.
7
+ #
8
+ # This plugin is mostly suited to test code. If there is any
9
+ # chance that your application's schema could change, you should
10
+ # be using the migration extension instead.
11
+ module Schema
12
+ module ClassMethods
13
+ # Creates table, using the column information from set_schema.
14
+ def create_table
15
+ db.create_table(table_name, :generator=>@schema)
16
+ @db_schema = get_db_schema(true)
17
+ columns
18
+ end
19
+
20
+ # Drops the table if it exists and then runs create_table. Should probably
21
+ # not be used except in testing.
22
+ def create_table!
23
+ drop_table rescue nil
24
+ create_table
25
+ end
26
+
27
+ # Creates the table unless the table already exists
28
+ def create_table?
29
+ create_table unless table_exists?
30
+ end
31
+
32
+ # Drops table.
33
+ def drop_table
34
+ db.drop_table(table_name)
35
+ end
36
+
37
+ # Returns table schema created with set_schema for direct descendant of Model.
38
+ # Does not retreive schema information from the database, see db_schema if you
39
+ # want that.
40
+ def schema
41
+ @schema || (superclass.schema unless superclass == Model)
42
+ end
43
+
44
+ # Defines a table schema (see Schema::Generator for more information).
45
+ #
46
+ # This is only needed if you want to use the create_table/create_table! methods.
47
+ # Will also set the dataset if you provide a name, as well as setting
48
+ # the primary key if you defined one in the passed block.
49
+ #
50
+ # In general, it is a better idea to use migrations for production code, as
51
+ # migrations allow changes to existing schema. set_schema is mostly useful for
52
+ # test code or simple examples.
53
+ def set_schema(name = nil, &block)
54
+ set_dataset(db[name]) if name
55
+ @schema = Sequel::Schema::Generator.new(db, &block)
56
+ set_primary_key(@schema.primary_key_name) if @schema.primary_key_name
57
+ end
58
+
59
+ # Returns true if table exists, false otherwise.
60
+ def table_exists?
61
+ db.table_exists?(table_name)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,166 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel's built in Serialization plugin allows you to keep serialized
4
+ # ruby objects in the database, while giving you deserialized objects
5
+ # when you call an accessor.
6
+ #
7
+ # This plugin works by keeping the serialized value in the values, and
8
+ # adding a @deserialized_values hash. The reader method for serialized columns
9
+ # will check the @deserialized_values for the value, return it if present,
10
+ # or deserialized the entry in @values and return it. The writer method will
11
+ # set the @deserialized_values entry. This plugin adds a before_save hook
12
+ # that serializes all @deserialized_values to @values.
13
+ #
14
+ # You can use either marshal, yaml, or json as the serialization format.
15
+ # If you use yaml or json, you should require them by yourself.
16
+ #
17
+ # Because of how this plugin works, it must be used inside each model class
18
+ # that needs serialization, after any set_dataset method calls in that class.
19
+ # Otherwise, it is possible that the default column accessors will take
20
+ # precedence.
21
+ #
22
+ # == Example
23
+ #
24
+ # require 'sequel'
25
+ # require 'json'
26
+ # class User < Sequel::Model
27
+ # plugin :serialization, :json, :permissions
28
+ # # or
29
+ # plugin :serialization
30
+ # serialize_attributes :marshal, :permissions, :attributes
31
+ # end
32
+ # user = User.create
33
+ # user.permissions = { :global => 'read-only' }
34
+ # user.save
35
+ module Serialization
36
+ # Set up the column readers to do deserialization and the column writers
37
+ # to save the value in deserialized_values.
38
+ def self.apply(model, *args)
39
+ model.instance_eval{@serialization_map = {}}
40
+ end
41
+
42
+ def self.configure(model, format=nil, *columns)
43
+ model.serialize_attributes(format, *columns) unless columns.empty?
44
+ end
45
+
46
+ module ClassMethods
47
+ # A map of the serialized columns for this model. Keys are column
48
+ # symbols, values are serialization formats (:marshal, :yaml, or :json).
49
+ attr_reader :serialization_map
50
+
51
+ # Module to store the serialized column accessor methods, so they can
52
+ # call be overridden and call super to get the serialization behavior
53
+ attr_accessor :serialization_module
54
+
55
+ # Copy the serialization format and columns to serialize into the subclass.
56
+ def inherited(subclass)
57
+ super
58
+ sm = serialization_map.dup
59
+ subclass.instance_eval{@serialization_map = sm}
60
+ end
61
+
62
+ # The first value in the serialization map. This is only for
63
+ # backwards compatibility, use serialization_map in new code.
64
+ def serialization_format
65
+ serialization_map.values.first
66
+ end
67
+
68
+ # Create instance level reader that deserializes column values on request,
69
+ # and instance level writer that stores new deserialized value in deserialized
70
+ # columns
71
+ def serialize_attributes(format, *columns)
72
+ raise(Error, "Unsupported serialization format (#{format}), should be :marshal, :yaml, or :json") unless [:marshal, :yaml, :json].include?(format)
73
+ raise(Error, "No columns given. The serialization plugin requires you specify which columns to serialize") if columns.empty?
74
+ define_serialized_attribute_accessor(format, *columns)
75
+ end
76
+
77
+ # The columns that will be serialized. This is only for
78
+ # backwards compatibility, use serialization_map in new code.
79
+ def serialized_columns
80
+ serialization_map.keys
81
+ end
82
+
83
+ private
84
+
85
+ # Add serializated attribute acessor methods to the serialization_module
86
+ def define_serialized_attribute_accessor(format, *columns)
87
+ m = self
88
+ include(self.serialization_module ||= Module.new) unless serialization_module
89
+ serialization_module.class_eval do
90
+ columns.each do |column|
91
+ m.serialization_map[column] = format
92
+ define_method(column) do
93
+ if deserialized_values.has_key?(column)
94
+ deserialized_values[column]
95
+ else
96
+ deserialized_values[column] = deserialize_value(column, super())
97
+ end
98
+ end
99
+ define_method("#{column}=") do |v|
100
+ changed_columns << column unless changed_columns.include?(column)
101
+ deserialized_values[column] = v
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ module InstanceMethods
109
+ # Hash of deserialized values, used as a cache.
110
+ attr_reader :deserialized_values
111
+
112
+ # Set @deserialized_values to the empty hash
113
+ def initialize(*args, &block)
114
+ @deserialized_values = {}
115
+ super
116
+ end
117
+
118
+ # Serialize all deserialized values
119
+ def before_save
120
+ super
121
+ deserialized_values.each do |k,v|
122
+ @values[k] = serialize_value(k, v)
123
+ end
124
+ end
125
+
126
+ # Empty the deserialized values when refreshing.
127
+ def refresh
128
+ @deserialized_values = {}
129
+ super
130
+ end
131
+
132
+ private
133
+
134
+ # Deserialize the column from either marshal or yaml format
135
+ def deserialize_value(column, v)
136
+ return v if v.nil?
137
+ case model.serialization_map[column]
138
+ when :marshal
139
+ Marshal.load(v.unpack('m')[0]) rescue Marshal.load(v)
140
+ when :yaml
141
+ YAML.load v if v
142
+ when :json
143
+ JSON.parse v if v
144
+ else
145
+ raise Error, "Bad serialization format (#{model.serialization_map[column].inspect}) for column #{column.inspect}"
146
+ end
147
+ end
148
+
149
+ # Serialize the column to either marshal or yaml format
150
+ def serialize_value(column, v)
151
+ return v if v.nil?
152
+ case model.serialization_map[column]
153
+ when :marshal
154
+ [Marshal.dump(v)].pack('m')
155
+ when :yaml
156
+ v.to_yaml
157
+ when :json
158
+ JSON.generate v
159
+ else
160
+ raise Error, "Bad serialization format (#{model.serialization_map[column].inspect}) for column #{column.inspect}"
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end