sequel_core 2.2.0 → 3.8.0
Sign up to get free protection for your applications and to get access to all the features.
- metadata +30 -101
- data/CHANGELOG +0 -1519
- data/COPYING +0 -19
- data/README +0 -313
- data/Rakefile +0 -158
- data/bin/sequel +0 -117
- data/doc/cheat_sheet.rdoc +0 -225
- data/doc/dataset_filtering.rdoc +0 -182
- data/lib/sequel_core.rb +0 -136
- data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -68
- data/lib/sequel_core/adapters/ado.rb +0 -90
- data/lib/sequel_core/adapters/db2.rb +0 -160
- data/lib/sequel_core/adapters/dbi.rb +0 -127
- data/lib/sequel_core/adapters/informix.rb +0 -89
- data/lib/sequel_core/adapters/jdbc.rb +0 -110
- data/lib/sequel_core/adapters/mysql.rb +0 -486
- data/lib/sequel_core/adapters/odbc.rb +0 -167
- data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
- data/lib/sequel_core/adapters/openbase.rb +0 -76
- data/lib/sequel_core/adapters/oracle.rb +0 -182
- data/lib/sequel_core/adapters/postgres.rb +0 -560
- data/lib/sequel_core/adapters/sqlite.rb +0 -270
- data/lib/sequel_core/connection_pool.rb +0 -194
- data/lib/sequel_core/core_ext.rb +0 -197
- data/lib/sequel_core/core_sql.rb +0 -184
- data/lib/sequel_core/database.rb +0 -462
- data/lib/sequel_core/database/schema.rb +0 -156
- data/lib/sequel_core/dataset.rb +0 -457
- data/lib/sequel_core/dataset/callback.rb +0 -13
- data/lib/sequel_core/dataset/convenience.rb +0 -245
- data/lib/sequel_core/dataset/pagination.rb +0 -96
- data/lib/sequel_core/dataset/query.rb +0 -41
- data/lib/sequel_core/dataset/schema.rb +0 -15
- data/lib/sequel_core/dataset/sql.rb +0 -889
- data/lib/sequel_core/deprecated.rb +0 -26
- data/lib/sequel_core/exceptions.rb +0 -42
- data/lib/sequel_core/migration.rb +0 -187
- data/lib/sequel_core/object_graph.rb +0 -216
- data/lib/sequel_core/pretty_table.rb +0 -71
- data/lib/sequel_core/schema.rb +0 -2
- data/lib/sequel_core/schema/generator.rb +0 -239
- data/lib/sequel_core/schema/sql.rb +0 -326
- data/lib/sequel_core/sql.rb +0 -812
- data/lib/sequel_core/worker.rb +0 -68
- data/spec/adapters/informix_spec.rb +0 -96
- data/spec/adapters/mysql_spec.rb +0 -765
- data/spec/adapters/oracle_spec.rb +0 -222
- data/spec/adapters/postgres_spec.rb +0 -441
- data/spec/adapters/sqlite_spec.rb +0 -413
- data/spec/connection_pool_spec.rb +0 -363
- data/spec/core_ext_spec.rb +0 -156
- data/spec/core_sql_spec.rb +0 -427
- data/spec/database_spec.rb +0 -963
- data/spec/dataset_spec.rb +0 -2933
- data/spec/expression_filters_spec.rb +0 -316
- data/spec/migration_spec.rb +0 -261
- data/spec/object_graph_spec.rb +0 -230
- data/spec/pretty_table_spec.rb +0 -58
- data/spec/rcov.opts +0 -6
- data/spec/schema_generator_spec.rb +0 -122
- data/spec/schema_spec.rb +0 -422
- data/spec/spec.opts +0 -0
- data/spec/spec_config.rb +0 -7
- data/spec/spec_config.rb.example +0 -8
- data/spec/spec_helper.rb +0 -55
- data/spec/worker_spec.rb +0 -96
@@ -1,156 +0,0 @@
|
|
1
|
-
module Sequel
|
2
|
-
class Database
|
3
|
-
# Adds a column to the specified table. This method expects a column name,
|
4
|
-
# a datatype and optionally a hash with additional constraints and options:
|
5
|
-
#
|
6
|
-
# DB.add_column :items, :name, :text, :unique => true, :null => false
|
7
|
-
# DB.add_column :items, :category, :text, :default => 'ruby'
|
8
|
-
#
|
9
|
-
# See alter_table.
|
10
|
-
def add_column(table, *args)
|
11
|
-
alter_table(table) {add_column(*args)}
|
12
|
-
end
|
13
|
-
|
14
|
-
# Adds an index to a table for the given columns:
|
15
|
-
#
|
16
|
-
# DB.add_index :posts, :title
|
17
|
-
# DB.add_index :posts, [:author, :title], :unique => true
|
18
|
-
#
|
19
|
-
# See alter_table.
|
20
|
-
def add_index(table, *args)
|
21
|
-
alter_table(table) {add_index(*args)}
|
22
|
-
end
|
23
|
-
|
24
|
-
# Alters the given table with the specified block. Here are the currently
|
25
|
-
# available operations:
|
26
|
-
#
|
27
|
-
# DB.alter_table :items do
|
28
|
-
# add_column :category, :text, :default => 'ruby'
|
29
|
-
# drop_column :category
|
30
|
-
# rename_column :cntr, :counter
|
31
|
-
# set_column_type :value, :float
|
32
|
-
# set_column_default :value, :float
|
33
|
-
# add_index [:group, :category]
|
34
|
-
# drop_index [:group, :category]
|
35
|
-
# end
|
36
|
-
#
|
37
|
-
# Note that #add_column accepts all the options available for column
|
38
|
-
# definitions using create_table, and #add_index accepts all the options
|
39
|
-
# available for index definition.
|
40
|
-
#
|
41
|
-
# See Schema::AlterTableGenerator.
|
42
|
-
def alter_table(name, &block)
|
43
|
-
g = Schema::AlterTableGenerator.new(self, &block)
|
44
|
-
alter_table_sql_list(name, g.operations).each {|sql| execute(sql)}
|
45
|
-
end
|
46
|
-
|
47
|
-
# Creates a table with the columns given in the provided block:
|
48
|
-
#
|
49
|
-
# DB.create_table :posts do
|
50
|
-
# primary_key :id, :serial
|
51
|
-
# column :title, :text
|
52
|
-
# column :content, :text
|
53
|
-
# index :title
|
54
|
-
# end
|
55
|
-
#
|
56
|
-
# See Schema::Generator.
|
57
|
-
def create_table(name, &block)
|
58
|
-
g = Schema::Generator.new(self, &block)
|
59
|
-
create_table_sql_list(name, *g.create_info).each {|sql| execute(sql)}
|
60
|
-
end
|
61
|
-
|
62
|
-
# Forcibly creates a table. If the table already exists it is dropped.
|
63
|
-
def create_table!(name, &block)
|
64
|
-
drop_table(name) rescue nil
|
65
|
-
create_table(name, &block)
|
66
|
-
end
|
67
|
-
|
68
|
-
# Creates a view, replacing it if it already exists:
|
69
|
-
#
|
70
|
-
# DB.create_or_replace_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
|
71
|
-
# DB.create_or_replace_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
|
72
|
-
def create_or_replace_view(name, source)
|
73
|
-
source = source.sql if source.is_a?(Dataset)
|
74
|
-
execute("CREATE OR REPLACE VIEW #{name} AS #{source}")
|
75
|
-
end
|
76
|
-
|
77
|
-
# Creates a view based on a dataset or an SQL string:
|
78
|
-
#
|
79
|
-
# DB.create_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
|
80
|
-
# DB.create_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
|
81
|
-
def create_view(name, source)
|
82
|
-
source = source.sql if source.is_a?(Dataset)
|
83
|
-
execute("CREATE VIEW #{name} AS #{source}")
|
84
|
-
end
|
85
|
-
|
86
|
-
# Removes a column from the specified table:
|
87
|
-
#
|
88
|
-
# DB.drop_column :items, :category
|
89
|
-
#
|
90
|
-
# See alter_table.
|
91
|
-
def drop_column(table, *args)
|
92
|
-
alter_table(table) {drop_column(*args)}
|
93
|
-
end
|
94
|
-
|
95
|
-
# Removes an index for the given table and column/s:
|
96
|
-
#
|
97
|
-
# DB.drop_index :posts, :title
|
98
|
-
# DB.drop_index :posts, [:author, :title]
|
99
|
-
#
|
100
|
-
# See alter_table.
|
101
|
-
def drop_index(table, columns)
|
102
|
-
alter_table(table) {drop_index(columns)}
|
103
|
-
end
|
104
|
-
|
105
|
-
# Drops one or more tables corresponding to the given table names:
|
106
|
-
#
|
107
|
-
# DB.drop_table(:posts, :comments)
|
108
|
-
def drop_table(*names)
|
109
|
-
names.each {|n| execute(drop_table_sql(n))}
|
110
|
-
end
|
111
|
-
|
112
|
-
# Drops a view:
|
113
|
-
#
|
114
|
-
# DB.drop_view(:cheap_items)
|
115
|
-
def drop_view(name)
|
116
|
-
execute("DROP VIEW #{name}")
|
117
|
-
end
|
118
|
-
|
119
|
-
# Renames a table:
|
120
|
-
#
|
121
|
-
# DB.tables #=> [:items]
|
122
|
-
# DB.rename_table :items, :old_items
|
123
|
-
# DB.tables #=> [:old_items]
|
124
|
-
def rename_table(*args)
|
125
|
-
execute(rename_table_sql(*args))
|
126
|
-
end
|
127
|
-
|
128
|
-
# Renames a column in the specified table. This method expects the current
|
129
|
-
# column name and the new column name:
|
130
|
-
#
|
131
|
-
# DB.rename_column :items, :cntr, :counter
|
132
|
-
#
|
133
|
-
# See alter_table.
|
134
|
-
def rename_column(table, *args)
|
135
|
-
alter_table(table) {rename_column(*args)}
|
136
|
-
end
|
137
|
-
|
138
|
-
# Sets the default value for the given column in the given table:
|
139
|
-
#
|
140
|
-
# DB.set_column_default :items, :category, 'perl!'
|
141
|
-
#
|
142
|
-
# See alter_table.
|
143
|
-
def set_column_default(table, *args)
|
144
|
-
alter_table(table) {set_column_default(*args)}
|
145
|
-
end
|
146
|
-
|
147
|
-
# Set the data type for the given column in the given table:
|
148
|
-
#
|
149
|
-
# DB.set_column_type :items, :price, :float
|
150
|
-
#
|
151
|
-
# See alter_table.
|
152
|
-
def set_column_type(table, *args)
|
153
|
-
alter_table(table) {set_column_type(*args)}
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
data/lib/sequel_core/dataset.rb
DELETED
@@ -1,457 +0,0 @@
|
|
1
|
-
%w'callback convenience pagination query schema sql'.each do |f|
|
2
|
-
require "sequel_core/dataset/#{f}"
|
3
|
-
end
|
4
|
-
|
5
|
-
module Sequel
|
6
|
-
# A Dataset represents a view of a the data in a database, constrained by
|
7
|
-
# specific parameters such as filtering conditions, order, etc. Datasets
|
8
|
-
# can be used to create, retrieve, update and delete records.
|
9
|
-
#
|
10
|
-
# Query results are always retrieved on demand, so a dataset can be kept
|
11
|
-
# around and reused indefinitely:
|
12
|
-
#
|
13
|
-
# my_posts = DB[:posts].filter(:author => 'david') # no records are retrieved
|
14
|
-
# p my_posts.all # records are now retrieved
|
15
|
-
# ...
|
16
|
-
# p my_posts.all # records are retrieved again
|
17
|
-
#
|
18
|
-
# In order to provide this functionality, dataset methods such as where,
|
19
|
-
# select, order, etc. return modified copies of the dataset, so you can
|
20
|
-
# use different datasets to access data:
|
21
|
-
#
|
22
|
-
# posts = DB[:posts]
|
23
|
-
# davids_posts = posts.filter(:author => 'david')
|
24
|
-
# old_posts = posts.filter('stamp < ?', Date.today - 7)
|
25
|
-
#
|
26
|
-
# Datasets are Enumerable objects, so they can be manipulated using any
|
27
|
-
# of the Enumerable methods, such as map, inject, etc.
|
28
|
-
#
|
29
|
-
# === The Dataset Adapter Interface
|
30
|
-
#
|
31
|
-
# Each adapter should define its own dataset class as a descendant of
|
32
|
-
# Sequel::Dataset. The following methods should be overridden by the adapter
|
33
|
-
# Dataset class (each method with the stock implementation):
|
34
|
-
#
|
35
|
-
# # Iterate over the results of the SQL query and call the supplied
|
36
|
-
# # block with each record (as a hash).
|
37
|
-
# def fetch_rows(sql, &block)
|
38
|
-
# @db.synchronize do
|
39
|
-
# r = @db.execute(sql)
|
40
|
-
# r.each(&block)
|
41
|
-
# end
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# # Insert records.
|
45
|
-
# def insert(*values)
|
46
|
-
# @db.synchronize do
|
47
|
-
# @db.execute(insert_sql(*values)).last_insert_id
|
48
|
-
# end
|
49
|
-
# end
|
50
|
-
#
|
51
|
-
# # Update records.
|
52
|
-
# def update(*args, &block)
|
53
|
-
# @db.synchronize do
|
54
|
-
# @db.execute(update_sql(*args, &block)).affected_rows
|
55
|
-
# end
|
56
|
-
# end
|
57
|
-
#
|
58
|
-
# # Delete records.
|
59
|
-
# def delete(opts = nil)
|
60
|
-
# @db.synchronize do
|
61
|
-
# @db.execute(delete_sql(opts)).affected_rows
|
62
|
-
# end
|
63
|
-
# end
|
64
|
-
#
|
65
|
-
# === Methods added via metaprogramming
|
66
|
-
#
|
67
|
-
# Some methods are added via metaprogramming:
|
68
|
-
#
|
69
|
-
# * ! methods - These methods are the same as their non-! counterparts,
|
70
|
-
# but they modify the receiver instead of returning a modified copy
|
71
|
-
# of the dataset.
|
72
|
-
# * inner_join, full_outer_join, right_outer_join, left_outer_join -
|
73
|
-
# This methods are shortcuts to join_table with the join type
|
74
|
-
# already specified.
|
75
|
-
class Dataset
|
76
|
-
include Enumerable
|
77
|
-
|
78
|
-
# The dataset options that require the removal of cached columns
|
79
|
-
# if changed.
|
80
|
-
COLUMN_CHANGE_OPTS = [:select, :sql, :from, :join].freeze
|
81
|
-
|
82
|
-
# Array of all subclasses of Dataset
|
83
|
-
DATASET_CLASSES = []
|
84
|
-
|
85
|
-
# All methods that should have a ! method added that modifies
|
86
|
-
# the receiver.
|
87
|
-
MUTATION_METHODS = %w'and distinct exclude exists filter from from_self full_outer_join graph
|
88
|
-
group group_and_count group_by having inner_join intersect invert join
|
89
|
-
left_outer_join limit naked or order order_by order_more paginate query reject
|
90
|
-
reverse reverse_order right_outer_join select select_all select_more
|
91
|
-
set_graph_aliases set_model sort sort_by unfiltered union unordered where'.collect{|x| x.to_sym}
|
92
|
-
|
93
|
-
NOTIMPL_MSG = "This method must be overridden in Sequel adapters".freeze
|
94
|
-
STOCK_TRANSFORMS = {
|
95
|
-
:marshal => [
|
96
|
-
# for backwards-compatibility we support also non-base64-encoded values.
|
97
|
-
proc {|v| Marshal.load(v.unpack('m')[0]) rescue Marshal.load(v)},
|
98
|
-
proc {|v| [Marshal.dump(v)].pack('m')}
|
99
|
-
],
|
100
|
-
:yaml => [
|
101
|
-
proc {|v| YAML.load v if v},
|
102
|
-
proc {|v| v.to_yaml}
|
103
|
-
]
|
104
|
-
}
|
105
|
-
|
106
|
-
# The database that corresponds to this dataset
|
107
|
-
attr_accessor :db
|
108
|
-
|
109
|
-
# The hash of options for this dataset, keys are symbols.
|
110
|
-
attr_accessor :opts
|
111
|
-
|
112
|
-
# The row_proc for this database, should be a Proc that takes
|
113
|
-
# a single hash argument and returns the object you want to
|
114
|
-
# fetch_rows to return.
|
115
|
-
attr_accessor :row_proc
|
116
|
-
|
117
|
-
# Whether to quote identifiers for this dataset
|
118
|
-
attr_writer :quote_identifiers
|
119
|
-
|
120
|
-
# Constructs a new instance of a dataset with an associated database and
|
121
|
-
# options. Datasets are usually constructed by invoking Database methods:
|
122
|
-
#
|
123
|
-
# DB[:posts]
|
124
|
-
#
|
125
|
-
# Or:
|
126
|
-
#
|
127
|
-
# DB.dataset # the returned dataset is blank
|
128
|
-
#
|
129
|
-
# Sequel::Dataset is an abstract class that is not useful by itself. Each
|
130
|
-
# database adaptor should provide a descendant class of Sequel::Dataset.
|
131
|
-
def initialize(db, opts = nil)
|
132
|
-
@db = db
|
133
|
-
@quote_identifiers = db.quote_identifiers? if db.respond_to?(:quote_identifiers?)
|
134
|
-
@opts = opts || {}
|
135
|
-
@row_proc = nil
|
136
|
-
@transform = nil
|
137
|
-
end
|
138
|
-
|
139
|
-
### Class Methods ###
|
140
|
-
|
141
|
-
# The array of dataset subclasses.
|
142
|
-
def self.dataset_classes
|
143
|
-
DATASET_CLASSES
|
144
|
-
end
|
145
|
-
|
146
|
-
# Setup mutation (e.g. filter!) methods. These operate the same as the
|
147
|
-
# non-! methods, but replace the options of the current dataset with the
|
148
|
-
# options of the resulting dataset.
|
149
|
-
def self.def_mutation_method(*meths)
|
150
|
-
meths.each do |meth|
|
151
|
-
class_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end")
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
# Add the subclass to the array of subclasses.
|
156
|
-
def self.inherited(c)
|
157
|
-
DATASET_CLASSES << c
|
158
|
-
end
|
159
|
-
|
160
|
-
### Instance Methods ###
|
161
|
-
|
162
|
-
# Alias for insert, but not aliased directly so subclasses
|
163
|
-
# don't have to override both methods.
|
164
|
-
def <<(*args)
|
165
|
-
insert(*args)
|
166
|
-
end
|
167
|
-
|
168
|
-
# Return the dataset as a column with the given alias, so it can be used in the
|
169
|
-
# SELECT clause. This dataset should result in a single row and a single column.
|
170
|
-
def as(aliaz)
|
171
|
-
::Sequel::SQL::AliasedExpression.new(self, aliaz)
|
172
|
-
end
|
173
|
-
|
174
|
-
# Returns an array with all records in the dataset. If a block is given,
|
175
|
-
# the array is iterated over after all items have been loaded.
|
176
|
-
def all(opts = nil, &block)
|
177
|
-
a = []
|
178
|
-
each(opts) {|r| a << r}
|
179
|
-
post_load(a)
|
180
|
-
a.each(&block) if block
|
181
|
-
a
|
182
|
-
end
|
183
|
-
|
184
|
-
# Returns a new clone of the dataset with with the given options merged.
|
185
|
-
# If the options changed include options in COLUMN_CHANGE_OPTS, the cached
|
186
|
-
# columns are deleted.
|
187
|
-
def clone(opts = {})
|
188
|
-
c = super()
|
189
|
-
c.opts = @opts.merge(opts)
|
190
|
-
c.instance_variable_set(:@columns, nil) if opts.keys.any?{|o| COLUMN_CHANGE_OPTS.include?(o)}
|
191
|
-
c
|
192
|
-
end
|
193
|
-
|
194
|
-
# Returns the columns in the result set in their true order.
|
195
|
-
# If the columns are currently cached, returns the cached value. Otherwise,
|
196
|
-
# a SELECT query is performed to get a single row. Adapters are expected
|
197
|
-
# to fill the columns cache with the column information when a query is performed.
|
198
|
-
# If the dataset does not have any rows, this will be an empty array.
|
199
|
-
# If you are looking for all columns for a single table, see Schema::SQL#schema.
|
200
|
-
def columns
|
201
|
-
return @columns if @columns
|
202
|
-
ds = unfiltered.unordered.clone(:distinct => nil)
|
203
|
-
ds.single_record
|
204
|
-
@columns = ds.instance_variable_get(:@columns)
|
205
|
-
@columns || []
|
206
|
-
end
|
207
|
-
|
208
|
-
# Remove the cached list of columns and do a SELECT query to find
|
209
|
-
# the columns.
|
210
|
-
def columns!
|
211
|
-
@columns = nil
|
212
|
-
columns
|
213
|
-
end
|
214
|
-
|
215
|
-
# Add a mutation method to this dataset instance.
|
216
|
-
def def_mutation_method(*meths)
|
217
|
-
meths.each do |meth|
|
218
|
-
instance_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end")
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
# Deletes the records in the dataset. Adapters should override this method.
|
223
|
-
def delete(opts = nil)
|
224
|
-
raise NotImplementedError, NOTIMPL_MSG
|
225
|
-
end
|
226
|
-
|
227
|
-
# Iterates over the records in the dataset.
|
228
|
-
def each(opts = nil, &block)
|
229
|
-
if graph = @opts[:graph]
|
230
|
-
graph_each(opts, &block)
|
231
|
-
else
|
232
|
-
row_proc = @row_proc unless opts && opts[:naked]
|
233
|
-
transform = @transform
|
234
|
-
fetch_rows(select_sql(opts)) do |r|
|
235
|
-
r = transform_load(r) if transform
|
236
|
-
r = row_proc[r] if row_proc
|
237
|
-
yield r
|
238
|
-
end
|
239
|
-
end
|
240
|
-
self
|
241
|
-
end
|
242
|
-
|
243
|
-
# Executes a select query and fetches records, passing each record to the
|
244
|
-
# supplied block. Adapters should override this method.
|
245
|
-
def fetch_rows(sql, &block)
|
246
|
-
raise NotImplementedError, NOTIMPL_MSG
|
247
|
-
end
|
248
|
-
|
249
|
-
# Inserts values into the associated table. Adapters should override this
|
250
|
-
# method.
|
251
|
-
def insert(*values)
|
252
|
-
raise NotImplementedError, NOTIMPL_MSG
|
253
|
-
end
|
254
|
-
|
255
|
-
# Returns a string representation of the dataset including the class name
|
256
|
-
# and the corresponding SQL select statement.
|
257
|
-
def inspect
|
258
|
-
"#<#{self.class}: #{sql.inspect}>"
|
259
|
-
end
|
260
|
-
|
261
|
-
# Returns the the model classes associated with the dataset as a hash.
|
262
|
-
# If the dataset is associated with a single model class, a key of nil
|
263
|
-
# is used. For datasets with polymorphic models, the keys are
|
264
|
-
# values of the polymorphic column and the values are the corresponding
|
265
|
-
# model classes to which they map.
|
266
|
-
def model_classes
|
267
|
-
@opts[:models]
|
268
|
-
end
|
269
|
-
|
270
|
-
# Returns a naked dataset clone - i.e. a dataset that returns records as
|
271
|
-
# hashes rather than model objects.
|
272
|
-
def naked
|
273
|
-
clone.set_model(nil)
|
274
|
-
end
|
275
|
-
|
276
|
-
# Returns the column name for the polymorphic key.
|
277
|
-
def polymorphic_key
|
278
|
-
@opts[:polymorphic_key]
|
279
|
-
end
|
280
|
-
|
281
|
-
# Whether this dataset quotes identifiers.
|
282
|
-
def quote_identifiers?
|
283
|
-
@quote_identifiers
|
284
|
-
end
|
285
|
-
|
286
|
-
# Alias for set, but not aliased directly so subclasses
|
287
|
-
# don't have to override both methods.
|
288
|
-
def set(*args, &block)
|
289
|
-
update(*args, &block)
|
290
|
-
end
|
291
|
-
|
292
|
-
# Associates or disassociates the dataset with a model(s). If
|
293
|
-
# nil is specified, the dataset is turned into a naked dataset and returns
|
294
|
-
# records as hashes. If a model class specified, the dataset is modified
|
295
|
-
# to return records as instances of the model class, e.g:
|
296
|
-
#
|
297
|
-
# class MyModel
|
298
|
-
# def initialize(values)
|
299
|
-
# @values = values
|
300
|
-
# ...
|
301
|
-
# end
|
302
|
-
# end
|
303
|
-
#
|
304
|
-
# dataset.set_model(MyModel)
|
305
|
-
#
|
306
|
-
# You can also provide additional arguments to be passed to the model's
|
307
|
-
# initialize method:
|
308
|
-
#
|
309
|
-
# class MyModel
|
310
|
-
# def initialize(values, options)
|
311
|
-
# @values = values
|
312
|
-
# ...
|
313
|
-
# end
|
314
|
-
# end
|
315
|
-
#
|
316
|
-
# dataset.set_model(MyModel, :allow_delete => false)
|
317
|
-
#
|
318
|
-
# The dataset can be made polymorphic by specifying a column name as the
|
319
|
-
# polymorphic key and a hash mapping column values to model classes.
|
320
|
-
#
|
321
|
-
# dataset.set_model(:kind, {1 => Person, 2 => Business})
|
322
|
-
#
|
323
|
-
# You can also set a default model class to fall back on by specifying a
|
324
|
-
# class corresponding to nil:
|
325
|
-
#
|
326
|
-
# dataset.set_model(:kind, {nil => DefaultClass, 1 => Person, 2 => Business})
|
327
|
-
#
|
328
|
-
# To make sure that there is always a default model class, the hash provided
|
329
|
-
# should have a default value. To make the dataset map string values to
|
330
|
-
# model classes, and keep a good default, try:
|
331
|
-
#
|
332
|
-
# dataset.set_model(:kind, Hash.new{|h,k| h[k] = (k.constantize rescue DefaultClass)})
|
333
|
-
def set_model(key, *args)
|
334
|
-
# This code is more verbose then necessary for performance reasons
|
335
|
-
case key
|
336
|
-
when nil # set_model(nil) => no argument provided, so the dataset is denuded
|
337
|
-
@opts.merge!(:naked => true, :models => nil, :polymorphic_key => nil)
|
338
|
-
self.row_proc = nil
|
339
|
-
when Class
|
340
|
-
# isomorphic model
|
341
|
-
@opts.merge!(:naked => nil, :models => {nil => key}, :polymorphic_key => nil)
|
342
|
-
if key.respond_to?(:load)
|
343
|
-
# the class has a values setter method, so we use it
|
344
|
-
self.row_proc = proc{|h| key.load(h, *args)}
|
345
|
-
else
|
346
|
-
# otherwise we just pass the hash to the constructor
|
347
|
-
self.row_proc = proc{|h| key.new(h, *args)}
|
348
|
-
end
|
349
|
-
when Symbol
|
350
|
-
# polymorphic model
|
351
|
-
hash = args.shift || raise(ArgumentError, "No class hash supplied for polymorphic model")
|
352
|
-
@opts.merge!(:naked => true, :models => hash, :polymorphic_key => key)
|
353
|
-
if (hash.empty? ? (hash[nil] rescue nil) : hash.values.first).respond_to?(:load)
|
354
|
-
# the class has a values setter method, so we use it
|
355
|
-
self.row_proc = proc do |h|
|
356
|
-
c = hash[h[key]] || hash[nil] || \
|
357
|
-
raise(Error, "No matching model class for record (#{polymorphic_key} => #{h[polymorphic_key].inspect})")
|
358
|
-
c.load(h, *args)
|
359
|
-
end
|
360
|
-
else
|
361
|
-
# otherwise we just pass the hash to the constructor
|
362
|
-
self.row_proc = proc do |h|
|
363
|
-
c = hash[h[key]] || hash[nil] || \
|
364
|
-
raise(Error, "No matching model class for record (#{polymorphic_key} => #{h[polymorphic_key].inspect})")
|
365
|
-
c.new(h, *args)
|
366
|
-
end
|
367
|
-
end
|
368
|
-
else
|
369
|
-
raise ArgumentError, "Invalid model specified"
|
370
|
-
end
|
371
|
-
self
|
372
|
-
end
|
373
|
-
|
374
|
-
# Sets a value transform which is used to convert values loaded and saved
|
375
|
-
# to/from the database. The transform should be supplied as a hash. Each
|
376
|
-
# value in the hash should be an array containing two proc objects - one
|
377
|
-
# for transforming loaded values, and one for transforming saved values.
|
378
|
-
# The following example demonstrates how to store Ruby objects in a dataset
|
379
|
-
# using Marshal serialization:
|
380
|
-
#
|
381
|
-
# dataset.transform(:obj => [
|
382
|
-
# proc {|v| Marshal.load(v)},
|
383
|
-
# proc {|v| Marshal.dump(v)}
|
384
|
-
# ])
|
385
|
-
#
|
386
|
-
# dataset.insert_sql(:obj => 1234) #=>
|
387
|
-
# "INSERT INTO items (obj) VALUES ('\004\bi\002\322\004')"
|
388
|
-
#
|
389
|
-
# Another form of using transform is by specifying stock transforms:
|
390
|
-
#
|
391
|
-
# dataset.transform(:obj => :marshal)
|
392
|
-
#
|
393
|
-
# The currently supported stock transforms are :marshal and :yaml.
|
394
|
-
def transform(t)
|
395
|
-
@transform = t
|
396
|
-
t.each do |k, v|
|
397
|
-
case v
|
398
|
-
when Array
|
399
|
-
if (v.size != 2) || !v.first.is_a?(Proc) && !v.last.is_a?(Proc)
|
400
|
-
raise Error::InvalidTransform, "Invalid transform specified"
|
401
|
-
end
|
402
|
-
else
|
403
|
-
unless v = STOCK_TRANSFORMS[v]
|
404
|
-
raise Error::InvalidTransform, "Invalid transform specified"
|
405
|
-
else
|
406
|
-
t[k] = v
|
407
|
-
end
|
408
|
-
end
|
409
|
-
end
|
410
|
-
self
|
411
|
-
end
|
412
|
-
|
413
|
-
# Applies the value transform for data loaded from the database.
|
414
|
-
def transform_load(r)
|
415
|
-
r.inject({}) do |m, kv|
|
416
|
-
k, v = *kv
|
417
|
-
m[k] = (tt = @transform[k]) ? tt[0][v] : v
|
418
|
-
m
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
# Applies the value transform for data saved to the database.
|
423
|
-
def transform_save(r)
|
424
|
-
r.inject({}) do |m, kv|
|
425
|
-
k, v = *kv
|
426
|
-
m[k] = (tt = @transform[k]) ? tt[1][v] : v
|
427
|
-
m
|
428
|
-
end
|
429
|
-
end
|
430
|
-
|
431
|
-
# Updates values for the dataset. Adapters should override this method.
|
432
|
-
def update(values, opts = nil)
|
433
|
-
raise NotImplementedError, NOTIMPL_MSG
|
434
|
-
end
|
435
|
-
|
436
|
-
# Add the mutation methods via metaprogramming
|
437
|
-
def_mutation_method(*MUTATION_METHODS)
|
438
|
-
|
439
|
-
protected
|
440
|
-
|
441
|
-
# Return true if the dataset has a non-nil value for any key in opts.
|
442
|
-
def options_overlap(opts)
|
443
|
-
!(@opts.collect{|k,v| k unless v.nil?}.compact & opts).empty?
|
444
|
-
end
|
445
|
-
|
446
|
-
private
|
447
|
-
|
448
|
-
# Modify the receiver with the results of sending the meth, args, and block
|
449
|
-
# to the receiver and merging the options of the resulting dataset into
|
450
|
-
# the receiver's options.
|
451
|
-
def mutation_method(meth, *args, &block)
|
452
|
-
copy = send(meth, *args, &block)
|
453
|
-
@opts.merge!(copy.opts)
|
454
|
-
self
|
455
|
-
end
|
456
|
-
end
|
457
|
-
end
|