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,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