sequel 3.11.0 → 3.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +70 -0
- data/Rakefile +1 -1
- data/doc/active_record.rdoc +896 -0
- data/doc/advanced_associations.rdoc +46 -31
- data/doc/association_basics.rdoc +14 -9
- data/doc/dataset_basics.rdoc +3 -3
- data/doc/migration.rdoc +1011 -0
- data/doc/model_hooks.rdoc +198 -0
- data/doc/querying.rdoc +811 -86
- data/doc/release_notes/3.12.0.txt +304 -0
- data/doc/sharding.rdoc +17 -0
- data/doc/sql.rdoc +537 -0
- data/doc/validations.rdoc +501 -0
- data/lib/sequel/adapters/jdbc.rb +19 -27
- data/lib/sequel/adapters/jdbc/postgresql.rb +0 -7
- data/lib/sequel/adapters/mysql.rb +5 -4
- data/lib/sequel/adapters/odbc.rb +3 -2
- data/lib/sequel/adapters/shared/mssql.rb +7 -6
- data/lib/sequel/adapters/shared/mysql.rb +2 -7
- data/lib/sequel/adapters/shared/postgres.rb +2 -8
- data/lib/sequel/adapters/shared/sqlite.rb +2 -5
- data/lib/sequel/adapters/sqlite.rb +4 -4
- data/lib/sequel/core.rb +0 -1
- data/lib/sequel/database.rb +2 -1060
- data/lib/sequel/database/connecting.rb +227 -0
- data/lib/sequel/database/dataset.rb +58 -0
- data/lib/sequel/database/dataset_defaults.rb +127 -0
- data/lib/sequel/database/logging.rb +62 -0
- data/lib/sequel/database/misc.rb +246 -0
- data/lib/sequel/database/query.rb +390 -0
- data/lib/sequel/database/schema_generator.rb +7 -3
- data/lib/sequel/database/schema_methods.rb +351 -7
- data/lib/sequel/dataset/actions.rb +9 -2
- data/lib/sequel/dataset/misc.rb +6 -2
- data/lib/sequel/dataset/mutation.rb +3 -11
- data/lib/sequel/dataset/query.rb +49 -6
- data/lib/sequel/exceptions.rb +3 -0
- data/lib/sequel/extensions/migration.rb +395 -113
- data/lib/sequel/extensions/schema_dumper.rb +21 -13
- data/lib/sequel/model.rb +27 -25
- data/lib/sequel/model/associations.rb +72 -34
- data/lib/sequel/model/base.rb +74 -18
- data/lib/sequel/model/errors.rb +8 -1
- data/lib/sequel/plugins/active_model.rb +8 -0
- data/lib/sequel/plugins/association_pks.rb +87 -0
- data/lib/sequel/plugins/association_proxies.rb +8 -0
- data/lib/sequel/plugins/boolean_readers.rb +12 -6
- data/lib/sequel/plugins/caching.rb +14 -7
- data/lib/sequel/plugins/class_table_inheritance.rb +15 -9
- data/lib/sequel/plugins/composition.rb +2 -1
- data/lib/sequel/plugins/force_encoding.rb +10 -7
- data/lib/sequel/plugins/hook_class_methods.rb +12 -11
- data/lib/sequel/plugins/identity_map.rb +9 -0
- data/lib/sequel/plugins/instance_hooks.rb +23 -13
- data/lib/sequel/plugins/lazy_attributes.rb +4 -1
- data/lib/sequel/plugins/many_through_many.rb +18 -4
- data/lib/sequel/plugins/nested_attributes.rb +1 -0
- data/lib/sequel/plugins/optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +9 -8
- data/lib/sequel/plugins/schema.rb +8 -0
- data/lib/sequel/plugins/serialization.rb +1 -3
- data/lib/sequel/plugins/sharding.rb +135 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +117 -25
- data/lib/sequel/plugins/skip_create_refresh.rb +35 -0
- data/lib/sequel/plugins/string_stripper.rb +26 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +8 -0
- data/lib/sequel/plugins/timestamps.rb +15 -2
- data/lib/sequel/plugins/touch.rb +13 -0
- data/lib/sequel/plugins/update_primary_key.rb +48 -0
- data/lib/sequel/plugins/validation_class_methods.rb +8 -0
- data/lib/sequel/plugins/validation_helpers.rb +1 -1
- data/lib/sequel/sql.rb +17 -20
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +5 -5
- data/spec/core/core_sql_spec.rb +17 -1
- data/spec/core/database_spec.rb +17 -5
- data/spec/core/dataset_spec.rb +31 -8
- data/spec/core/schema_generator_spec.rb +8 -1
- data/spec/core/schema_spec.rb +13 -0
- data/spec/extensions/association_pks_spec.rb +85 -0
- data/spec/extensions/hook_class_methods_spec.rb +9 -9
- data/spec/extensions/migration_spec.rb +339 -219
- data/spec/extensions/schema_dumper_spec.rb +28 -17
- data/spec/extensions/sharding_spec.rb +272 -0
- data/spec/extensions/single_table_inheritance_spec.rb +92 -4
- data/spec/extensions/skip_create_refresh_spec.rb +17 -0
- data/spec/extensions/string_stripper_spec.rb +23 -0
- data/spec/extensions/update_primary_key_spec.rb +65 -0
- data/spec/extensions/validation_class_methods_spec.rb +5 -5
- data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
- data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
- data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
- data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
- data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
- data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
- data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
- data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +4 -0
- data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
- data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
- data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
- data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
- data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +4 -0
- data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +9 -0
- data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +9 -0
- data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +4 -0
- data/spec/integration/eager_loader_test.rb +20 -20
- data/spec/integration/migrator_test.rb +187 -0
- data/spec/integration/plugin_test.rb +150 -0
- data/spec/integration/schema_test.rb +13 -2
- data/spec/model/associations_spec.rb +41 -14
- data/spec/model/base_spec.rb +69 -0
- data/spec/model/eager_loading_spec.rb +7 -3
- data/spec/model/record_spec.rb +79 -4
- data/spec/model/validations_spec.rb +21 -9
- metadata +66 -5
- data/doc/schema.rdoc +0 -36
- data/lib/sequel/database/schema_sql.rb +0 -320
@@ -13,12 +13,12 @@ module Sequel
|
|
13
13
|
def dump_indexes_migration(options={})
|
14
14
|
ts = tables(options)
|
15
15
|
<<END_MIG
|
16
|
-
|
17
|
-
|
16
|
+
Sequel.migration do
|
17
|
+
up do
|
18
18
|
#{ts.sort_by{|t| t.to_s}.map{|t| dump_table_indexes(t, :add_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
|
19
19
|
end
|
20
20
|
|
21
|
-
|
21
|
+
down do
|
22
22
|
#{ts.sort_by{|t| t.to_s}.map{|t| dump_table_indexes(t, :drop_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
|
23
23
|
end
|
24
24
|
end
|
@@ -37,12 +37,12 @@ END_MIG
|
|
37
37
|
def dump_schema_migration(options={})
|
38
38
|
ts = tables(options)
|
39
39
|
<<END_MIG
|
40
|
-
|
41
|
-
|
40
|
+
Sequel.migration do
|
41
|
+
up do
|
42
42
|
#{ts.sort_by{|t| t.to_s}.map{|t| dump_table_schema(t, options)}.join("\n\n").gsub(/^/o, ' ')}
|
43
43
|
end
|
44
44
|
|
45
|
-
|
45
|
+
down do
|
46
46
|
drop_table(#{ts.sort_by{|t| t.to_s}.inspect[1...-1]})
|
47
47
|
end
|
48
48
|
end
|
@@ -52,12 +52,18 @@ END_MIG
|
|
52
52
|
# Return a string with a create table block that will recreate the given
|
53
53
|
# table's schema. Takes the same options as dump_schema_migration.
|
54
54
|
def dump_table_schema(table, options={})
|
55
|
+
table = table.value.to_s if table.is_a?(SQL::Identifier)
|
56
|
+
raise(Error, "must provide table as a Symbol, String, or Sequel::SQL::Identifier") unless [String, Symbol].any?{|c| table.is_a?(c)}
|
55
57
|
s = schema(table).dup
|
56
58
|
pks = s.find_all{|x| x.last[:primary_key] == true}.map{|x| x.first}
|
57
59
|
options = options.merge(:single_pk=>true) if pks.length == 1
|
58
60
|
m = method(:column_schema_to_generator_opts)
|
59
61
|
im = method(:index_to_generator_opts)
|
60
|
-
|
62
|
+
begin
|
63
|
+
indexes = indexes(table).sort_by{|k,v| k.to_s} if options[:indexes] != false
|
64
|
+
rescue Sequel::NotImplemented
|
65
|
+
nil
|
66
|
+
end
|
61
67
|
gen = Schema::Generator.new(self) do
|
62
68
|
s.each{|name, info| send(*m.call(name, info, options))}
|
63
69
|
primary_key(pks) if !@primary_key && pks.length > 0
|
@@ -128,8 +134,8 @@ END_MIG
|
|
128
134
|
{:type=>Date}
|
129
135
|
when /\A(?:small)?datetime\z/o
|
130
136
|
{:type=>DateTime}
|
131
|
-
when /\Atimestamp(?: with(?:out)? time zone)?\z/o
|
132
|
-
{:type=>DateTime}
|
137
|
+
when /\Atimestamp(?:\((\d+)\))?(?: with(?:out)? time zone)?\z/o
|
138
|
+
{:type=>DateTime, :size=>($1.to_i if $1)}
|
133
139
|
when /\Atime(?: with(?:out)? time zone)?\z/o
|
134
140
|
{:type=>Time, :only_time=>true}
|
135
141
|
when /\An?char(?:acter)?(?:\((\d+)\))?\z/o
|
@@ -208,7 +214,7 @@ END_MIG
|
|
208
214
|
# Dump this generator's constraints to a string that could be evaled inside
|
209
215
|
# another instance to represent the same constraints
|
210
216
|
def dump_constraints
|
211
|
-
constraints.map do |c|
|
217
|
+
cs = constraints.map do |c|
|
212
218
|
c = c.dup
|
213
219
|
type = c.delete(:type)
|
214
220
|
case type
|
@@ -224,7 +230,8 @@ END_MIG
|
|
224
230
|
cols = c.delete(:columns)
|
225
231
|
"#{type} #{cols.inspect}#{opts_inspect(c)}"
|
226
232
|
end
|
227
|
-
end
|
233
|
+
end
|
234
|
+
cs.join("\n")
|
228
235
|
end
|
229
236
|
|
230
237
|
# Dump this generator's indexes to a string that could be evaled inside
|
@@ -235,7 +242,7 @@ END_MIG
|
|
235
242
|
# * :drop_index - Same as add_index, but create drop_index statements.
|
236
243
|
# * :ignore_errors - Add the ignore_errors option to the outputted indexes
|
237
244
|
def dump_indexes(options={})
|
238
|
-
indexes.map do |c|
|
245
|
+
is = indexes.map do |c|
|
239
246
|
c = c.dup
|
240
247
|
cols = c.delete(:columns)
|
241
248
|
if table = options[:add_index] || options[:drop_index]
|
@@ -243,7 +250,8 @@ END_MIG
|
|
243
250
|
else
|
244
251
|
"index #{cols.inspect}#{opts_inspect(c)}"
|
245
252
|
end
|
246
|
-
end
|
253
|
+
end
|
254
|
+
is.join("\n")
|
247
255
|
end
|
248
256
|
|
249
257
|
private
|
data/lib/sequel/model.rb
CHANGED
@@ -44,28 +44,30 @@ module Sequel
|
|
44
44
|
ANONYMOUS_MODEL_CLASSES = {}
|
45
45
|
|
46
46
|
# Class methods added to model that call the method of the same name on the dataset
|
47
|
-
DATASET_METHODS =
|
48
|
-
|
49
|
-
|
50
|
-
inner_join insert insert_multiple intersect interval invert join join_table
|
51
|
-
last left_join left_outer_join limit lock_style map max min multi_insert naked
|
52
|
-
natural_full_join natural_join natural_left_join natural_right_join order order_by
|
53
|
-
order_more paginate print qualify query range reverse reverse_order right_join right_outer_join
|
54
|
-
select select_all select_append select_hash select_map select_more select_order_map
|
55
|
-
server set set_defaults set_graph_aliases set_overrides
|
56
|
-
single_value sum to_csv to_hash truncate unfiltered ungraphed ungrouped union unlimited unordered
|
57
|
-
update where with with_recursive with_sql'.map{|x| x.to_sym}
|
58
|
-
|
47
|
+
DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
|
48
|
+
[:eager, :eager_graph, :each_page, :each_server, :print]) - [:and, :or, :[], :[]=, :columns, :columns!]
|
49
|
+
|
59
50
|
# Class instance variables to set to nil when a subclass is created, for -w compliance
|
60
51
|
EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@db]
|
61
52
|
|
53
|
+
# Boolean settings that can be modified at the global, class, or instance level.
|
54
|
+
BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_transactions]
|
55
|
+
|
56
|
+
# Hooks that are called before an action. Can return false to not do the action. When
|
57
|
+
# overriding these, it is recommended to call super as the last line of your method,
|
58
|
+
# so later hooks are called before earlier hooks.
|
59
|
+
BEFORE_HOOKS = [:before_create, :before_update, :before_save, :before_destroy, :before_validation]
|
60
|
+
|
61
|
+
# Hooks that are called after an action. When overriding these, it is recommended to call
|
62
|
+
# super on the first line of your method, so later hooks are called before earlier hooks.
|
63
|
+
AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy, :after_validation]
|
64
|
+
|
62
65
|
# Empty instance methods to create that the user can override to get hook/callback behavior.
|
63
66
|
# Just like any other method defined by Sequel, if you override one of these, you should
|
64
67
|
# call super to get the default behavior (while empty by default, they can also be defined
|
65
|
-
# by plugins).
|
66
|
-
|
67
|
-
|
68
|
-
:before_validation, :after_validation]
|
68
|
+
# by plugins). See the {"Model Hooks" guide}[link:files/doc/model_hooks_rdoc.html] for
|
69
|
+
# more detail on hooks.
|
70
|
+
HOOKS = BEFORE_HOOKS + AFTER_HOOKS
|
69
71
|
|
70
72
|
# Class instance variables that are inherited in subclasses. If the value is :dup, dup is called
|
71
73
|
# on the superclass's instance variable when creating the instance variable in the subclass.
|
@@ -83,10 +85,6 @@ module Sequel
|
|
83
85
|
# avoid problems when using eval with a string to define methods.
|
84
86
|
NORMAL_METHOD_NAME_REGEXP = /\A[A-Za-z_][A-Za-z0-9_]*\z/
|
85
87
|
|
86
|
-
# The setter methods (methods ending with =) that are never allowed
|
87
|
-
# to be called automatically via set/update/new/etc..
|
88
|
-
RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure= raise_on_typecast_failure="
|
89
|
-
|
90
88
|
# Regular expression that determines if the method is a valid setter name
|
91
89
|
# (i.e. it ends with =).
|
92
90
|
SETTER_METHOD_REGEXP = /=\z/
|
@@ -110,11 +108,15 @@ module Sequel
|
|
110
108
|
@typecast_empty_string_to_nil = true
|
111
109
|
@typecast_on_assignment = true
|
112
110
|
@use_transactions = true
|
113
|
-
end
|
114
111
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
112
|
+
Sequel.require %w"default_inflections inflections plugins base exceptions errors", "model"
|
113
|
+
if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
|
114
|
+
Sequel.require 'associations', 'model'
|
115
|
+
plugin Model::Associations
|
116
|
+
end
|
117
|
+
|
118
|
+
# The setter methods (methods ending with =) that are never allowed
|
119
|
+
# to be called automatically via set/update/new/etc..
|
120
|
+
RESTRICTED_SETTER_METHODS = instance_methods.map{|x| x.to_s}.grep(SETTER_METHOD_REGEXP)
|
119
121
|
end
|
120
122
|
end
|
@@ -310,6 +310,11 @@ module Sequel
|
|
310
310
|
self[:join_table]
|
311
311
|
end
|
312
312
|
|
313
|
+
# Alias of right_primary_keys
|
314
|
+
def associated_object_keys
|
315
|
+
right_primary_keys
|
316
|
+
end
|
317
|
+
|
313
318
|
# many_to_many associations can only have associated objects if none of
|
314
319
|
# the :left_primary_keys options have a nil value.
|
315
320
|
def can_have_associated_objects?(obj)
|
@@ -380,7 +385,6 @@ module Sequel
|
|
380
385
|
def right_primary_keys
|
381
386
|
self[:right_primary_keys] ||= Array(right_primary_key)
|
382
387
|
end
|
383
|
-
alias associated_object_keys right_primary_keys
|
384
388
|
|
385
389
|
# The columns to select when loading the association, associated_class.table_name.* by default.
|
386
390
|
def select
|
@@ -441,8 +445,8 @@ module Sequel
|
|
441
445
|
# => {:type => :many_to_one, :name => :portfolio, :class_name => "Portfolio"}
|
442
446
|
#
|
443
447
|
# For a more in depth general overview, as well as a reference guide,
|
444
|
-
# see the {Association Basics
|
445
|
-
# For examples of advanced usage, see the {Advanced Associations
|
448
|
+
# see the {Association Basics guide}[link:files/doc/association_basics_rdoc.html].
|
449
|
+
# For examples of advanced usage, see the {Advanced Associations guide}[link:files/doc/advanced_associations_rdoc.html].
|
446
450
|
module ClassMethods
|
447
451
|
# All association reflections defined for this model (default: none).
|
448
452
|
attr_reader :association_reflections
|
@@ -521,9 +525,11 @@ module Sequel
|
|
521
525
|
# Takes three arguments, a dataset, an alias to use for the table to graph for this association,
|
522
526
|
# and the alias that was used for the current table (since you can cascade associations),
|
523
527
|
# Should return a copy of the dataset with the association graphed into it.
|
524
|
-
# - :eager_loader - A proc to use to implement eager loading, overriding the default. Takes three arguments
|
525
|
-
# a key hash (used solely to enhance performance), an array of records,
|
526
|
-
# and a hash of dependent associations.
|
528
|
+
# - :eager_loader - A proc to use to implement eager loading, overriding the default. Takes one or three arguments.
|
529
|
+
# If three arguments, the first should be a key hash (used solely to enhance performance), the second an array of records,
|
530
|
+
# and the third a hash of dependent associations. If one argument, is passed a hash with keys :key_hash,
|
531
|
+
# :rows, and :associations, corresponding to the three arguments, and an additional key :self, which is
|
532
|
+
# the dataset doing the eager loading. In the proc, the associated records should
|
527
533
|
# be queried from the database and the associations cache for each
|
528
534
|
# record should be populated for this to work correctly.
|
529
535
|
# - :eager_loader_key - A symbol for the key column to use to populate the key hash
|
@@ -593,6 +599,8 @@ module Sequel
|
|
593
599
|
# the current model and the associated model, as a symbol. Defaults to the name
|
594
600
|
# of current model and name of associated model, pluralized,
|
595
601
|
# underscored, sorted, and joined with '_'.
|
602
|
+
# - :join_table_block - proc that can be used to modify the dataset used in the add/remove/remove_all
|
603
|
+
# methods. Should accept a dataset argument and return a modified dataset if present.
|
596
604
|
# - :left_key - foreign key in join table that points to current model's
|
597
605
|
# primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
|
598
606
|
# Can use an array of symbols for a composite key association.
|
@@ -610,6 +618,7 @@ module Sequel
|
|
610
618
|
raise(Error, 'one_to_many association type with :one_to_one option removed, used one_to_one association type') if opts[:one_to_one] && type == :one_to_many
|
611
619
|
raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
|
612
620
|
raise(Error, 'Model.associate name argument must be a symbol') unless Symbol === name
|
621
|
+
raise(Error, ':eager_loader option must have an arity of 1 or 3') if opts[:eager_loader] && ![1, 3].include?(opts[:eager_loader].arity)
|
613
622
|
|
614
623
|
# merge early so we don't modify opts
|
615
624
|
orig_opts = opts.dup
|
@@ -649,7 +658,7 @@ module Sequel
|
|
649
658
|
end
|
650
659
|
|
651
660
|
# Modify and return eager loading dataset based on association options. Options:
|
652
|
-
def eager_loading_dataset(opts, ds, select, associations)
|
661
|
+
def eager_loading_dataset(opts, ds, select, associations, eager_options={})
|
653
662
|
ds = ds.select(*select) if select
|
654
663
|
if c = opts[:conditions]
|
655
664
|
ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
|
@@ -757,12 +766,12 @@ module Sequel
|
|
757
766
|
opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, rcks.zip(opts.right_primary_keys) + lcks.zip(lcpks.map{|k| send(k)}))}
|
758
767
|
database = db
|
759
768
|
|
760
|
-
opts[:eager_loader] ||= proc do |
|
761
|
-
h = key_hash[left_pk]
|
762
|
-
|
769
|
+
opts[:eager_loader] ||= proc do |eo|
|
770
|
+
h = eo[:key_hash][left_pk]
|
771
|
+
eo[:rows].each{|object| object.associations[name] = []}
|
763
772
|
r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
|
764
773
|
l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, SQL::SQLArray.new(h.keys)]] : [[left, h.keys]]
|
765
|
-
model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), Array(opts.select), associations).all do |assoc_record|
|
774
|
+
model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), Array(opts.select), eo[:associations], eo).all do |assoc_record|
|
766
775
|
hash_key = if uses_lcks
|
767
776
|
left_key_alias.map{|k| assoc_record.values.delete(k)}
|
768
777
|
else
|
@@ -796,13 +805,13 @@ module Sequel
|
|
796
805
|
h = {}
|
797
806
|
lcks.zip(lcpks).each{|k, pk| h[k] = send(pk)}
|
798
807
|
rcks.zip(opts.right_primary_keys).each{|k, pk| h[k] = o.send(pk)}
|
799
|
-
|
808
|
+
_join_table_dataset(opts).insert(h)
|
800
809
|
end
|
801
810
|
association_module_private_def(opts._remove_method) do |o|
|
802
|
-
|
811
|
+
_join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_keys.map{|k| o.send(k)})).delete
|
803
812
|
end
|
804
813
|
association_module_private_def(opts._remove_all_method) do
|
805
|
-
|
814
|
+
_join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)})).delete
|
806
815
|
end
|
807
816
|
|
808
817
|
def_add_method(opts)
|
@@ -823,16 +832,16 @@ module Sequel
|
|
823
832
|
klass = opts.associated_class
|
824
833
|
klass.filter(opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}.zip(cks.map{|k| send(k)}))
|
825
834
|
end
|
826
|
-
opts[:eager_loader] ||= proc do |
|
827
|
-
h = key_hash[key]
|
835
|
+
opts[:eager_loader] ||= proc do |eo|
|
836
|
+
h = eo[:key_hash][key]
|
828
837
|
keys = h.keys
|
829
838
|
# Default the cached association to nil, so any object that doesn't have it
|
830
839
|
# populated will have cached the negative lookup.
|
831
|
-
|
840
|
+
eo[:rows].each{|object| object.associations[name] = nil}
|
832
841
|
# Skip eager loading if no objects have a foreign key for this association
|
833
842
|
unless keys.empty?
|
834
843
|
klass = opts.associated_class
|
835
|
-
model.eager_loading_dataset(opts, klass.filter(uses_cks ? {opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>keys}), opts.select, associations).all do |assoc_record|
|
844
|
+
model.eager_loading_dataset(opts, klass.filter(uses_cks ? {opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>keys}), opts.select, eo[:associations], eo).all do |assoc_record|
|
836
845
|
hash_key = uses_cks ? opts.primary_keys.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key)
|
837
846
|
next unless objects = h[hash_key]
|
838
847
|
objects.each{|object| object.associations[name] = assoc_record}
|
@@ -873,16 +882,16 @@ module Sequel
|
|
873
882
|
klass = opts.associated_class
|
874
883
|
klass.filter(cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}.zip(cpks.map{|k| send(k)}))
|
875
884
|
end
|
876
|
-
opts[:eager_loader] ||= proc do |
|
877
|
-
h = key_hash[primary_key]
|
885
|
+
opts[:eager_loader] ||= proc do |eo|
|
886
|
+
h = eo[:key_hash][primary_key]
|
878
887
|
if one_to_one
|
879
|
-
|
888
|
+
eo[:rows].each{|object| object.associations[name] = nil}
|
880
889
|
else
|
881
|
-
|
890
|
+
eo[:rows].each{|object| object.associations[name] = []}
|
882
891
|
end
|
883
892
|
reciprocal = opts.reciprocal
|
884
893
|
klass = opts.associated_class
|
885
|
-
model.eager_loading_dataset(opts, klass.filter(uses_cks ? {cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(h.keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, key)=>h.keys}), opts.select, associations).all do |assoc_record|
|
894
|
+
model.eager_loading_dataset(opts, klass.filter(uses_cks ? {cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(h.keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, key)=>h.keys}), opts.select, eo[:associations], eo).all do |assoc_record|
|
886
895
|
hash_key = uses_cks ? cks.map{|k| assoc_record.send(k)} : assoc_record.send(key)
|
887
896
|
next unless objects = h[hash_key]
|
888
897
|
if one_to_one
|
@@ -928,11 +937,10 @@ module Sequel
|
|
928
937
|
up_ds = up_ds.exclude(o.pk_hash)
|
929
938
|
cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
|
930
939
|
end
|
931
|
-
|
940
|
+
checked_transaction do
|
932
941
|
up_ds.update(ck_nil_hash)
|
933
942
|
o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save") if o
|
934
943
|
end
|
935
|
-
use_transactions && o ? db.transaction(opts){update_database.call} : update_database.call
|
936
944
|
end
|
937
945
|
association_module_def(opts.setter_method){|o| set_one_to_one_associated_object(opts, o)}
|
938
946
|
else
|
@@ -953,7 +961,11 @@ module Sequel
|
|
953
961
|
end
|
954
962
|
end
|
955
963
|
end
|
956
|
-
|
964
|
+
|
965
|
+
# Alias of def_one_to_many
|
966
|
+
def def_one_to_one(opts)
|
967
|
+
def_one_to_many(opts)
|
968
|
+
end
|
957
969
|
|
958
970
|
# Add the remove_ and remove_all instance methods
|
959
971
|
def def_remove_methods(opts)
|
@@ -1005,6 +1017,12 @@ module Sequel
|
|
1005
1017
|
_apply_association_options(opts, send(opts._dataset_method))
|
1006
1018
|
end
|
1007
1019
|
|
1020
|
+
# Dataset for the join table of the given many to many association reflection
|
1021
|
+
def _join_table_dataset(opts)
|
1022
|
+
ds = model.db.from(opts[:join_table])
|
1023
|
+
opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
|
1024
|
+
end
|
1025
|
+
|
1008
1026
|
# Return the associated objects from the dataset, without callbacks, reciprocals, and caching.
|
1009
1027
|
def _load_associated_objects(opts)
|
1010
1028
|
if opts.returns_array?
|
@@ -1031,10 +1049,7 @@ module Sequel
|
|
1031
1049
|
raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
|
1032
1050
|
end
|
1033
1051
|
raise(Sequel::Error, "model object #{inspect} does not have a primary key") unless pk
|
1034
|
-
|
1035
|
-
o.save(:validate=>opts[:validate]) if o.new?
|
1036
|
-
raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") unless o.pk
|
1037
|
-
end
|
1052
|
+
ensure_associated_primary_key(opts, o, *args)
|
1038
1053
|
return if run_association_callbacks(opts, :before_add, o) == false
|
1039
1054
|
send(opts._add_method, o, *args)
|
1040
1055
|
if array = associations[opts[:name]] and !array.include?(o)
|
@@ -1063,6 +1078,16 @@ module Sequel
|
|
1063
1078
|
a.uniq!
|
1064
1079
|
end
|
1065
1080
|
|
1081
|
+
# Save the associated object if the associated object needs a primary key
|
1082
|
+
# and the associated object is new and does not have one. Raise an error if
|
1083
|
+
# the object still does not have a primary key
|
1084
|
+
def ensure_associated_primary_key(opts, o, *args)
|
1085
|
+
if opts.need_associated_primary_key?
|
1086
|
+
o.save(:validate=>opts[:validate]) if o.new?
|
1087
|
+
raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") unless o.pk
|
1088
|
+
end
|
1089
|
+
end
|
1090
|
+
|
1066
1091
|
# Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
|
1067
1092
|
def load_associated_objects(opts, reload=false)
|
1068
1093
|
name = opts[:name]
|
@@ -1095,9 +1120,7 @@ module Sequel
|
|
1095
1120
|
def remove_associated_object(opts, o, *args)
|
1096
1121
|
klass = opts.associated_class
|
1097
1122
|
if o.is_a?(Integer) || o.is_a?(String) || o.is_a?(Array)
|
1098
|
-
|
1099
|
-
pkh = klass.primary_key_hash(key)
|
1100
|
-
raise(Sequel::Error, "no object with key(s) #{key} is currently associated to #{inspect}") unless o = (opts.remove_should_check_existing? ? send(opts.dataset_method) : klass).first(pkh)
|
1123
|
+
o = remove_check_existing_object_from_pk(opts, o, *args)
|
1101
1124
|
elsif !o.is_a?(klass)
|
1102
1125
|
raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
|
1103
1126
|
elsif opts.remove_should_check_existing? && send(opts.dataset_method).filter(o.pk_hash).empty?
|
@@ -1113,6 +1136,16 @@ module Sequel
|
|
1113
1136
|
o
|
1114
1137
|
end
|
1115
1138
|
|
1139
|
+
# If necessary, check that the object from the associated table specified by the primary key
|
1140
|
+
# is currently associated to the object. If it is associated, return the object, otherwise
|
1141
|
+
# raise an error.
|
1142
|
+
def remove_check_existing_object_from_pk(opts, o, *args)
|
1143
|
+
key = o
|
1144
|
+
pkh = opts.associated_class.qualified_primary_key_hash(key)
|
1145
|
+
raise(Sequel::Error, "no object with key(s) #{key.inspect} is currently associated to #{inspect}") unless o = send(opts.dataset_method).first(pkh)
|
1146
|
+
o
|
1147
|
+
end
|
1148
|
+
|
1116
1149
|
# Remove/unset the current object from/as the given object's reciprocal association.
|
1117
1150
|
def remove_reciprocal_object(opts, o)
|
1118
1151
|
return unless reciprocal = opts.reciprocal
|
@@ -1498,7 +1531,12 @@ module Sequel
|
|
1498
1531
|
end
|
1499
1532
|
|
1500
1533
|
reflections.each do |r|
|
1501
|
-
|
1534
|
+
loader = r[:eager_loader]
|
1535
|
+
if loader.arity == 1
|
1536
|
+
loader.call(:key_hash=>key_hash, :rows=>a, :associations=>eager_assoc[r[:name]], :self=>self)
|
1537
|
+
else
|
1538
|
+
loader.call(key_hash, a, eager_assoc[r[:name]])
|
1539
|
+
end
|
1502
1540
|
a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
|
1503
1541
|
end
|
1504
1542
|
end
|
data/lib/sequel/model/base.rb
CHANGED
@@ -113,6 +113,11 @@ module Sequel
|
|
113
113
|
def dataset
|
114
114
|
@dataset || raise(Error, "No dataset associated with #{self}")
|
115
115
|
end
|
116
|
+
|
117
|
+
# Alias of set_dataset
|
118
|
+
def dataset=(ds)
|
119
|
+
set_dataset(ds)
|
120
|
+
end
|
116
121
|
|
117
122
|
# Returns the database associated with the Model class.
|
118
123
|
# If this model doesn't have a database associated with it,
|
@@ -236,6 +241,14 @@ module Sequel
|
|
236
241
|
{key => value}
|
237
242
|
end
|
238
243
|
end
|
244
|
+
|
245
|
+
# Return a hash where the keys are qualified column references. Uses the given
|
246
|
+
# qualifier if provided, or the table_name otherwise.
|
247
|
+
def qualified_primary_key_hash(value, qualifier=table_name)
|
248
|
+
h = primary_key_hash(value)
|
249
|
+
h.to_a.each{|k,v| h[SQL::QualifiedIdentifier.new(qualifier, k)] = h.delete(k)}
|
250
|
+
h
|
251
|
+
end
|
239
252
|
|
240
253
|
# Restrict the setting of the primary key(s) inside new/set/update. Because
|
241
254
|
# this is the default, this only make sense to use in a subclass where the
|
@@ -299,7 +312,6 @@ module Sequel
|
|
299
312
|
check_non_connection_error{@db_schema = (inherited ? superclass.db_schema : get_db_schema)}
|
300
313
|
self
|
301
314
|
end
|
302
|
-
alias dataset= set_dataset
|
303
315
|
|
304
316
|
# Sets the primary key for this model. You can use either a regular
|
305
317
|
# or a composite primary key.
|
@@ -524,7 +536,7 @@ module Sequel
|
|
524
536
|
private_class_method :class_attr_overridable, :class_attr_reader
|
525
537
|
|
526
538
|
class_attr_reader :columns, :db, :primary_key, :db_schema
|
527
|
-
class_attr_overridable
|
539
|
+
class_attr_overridable *BOOLEAN_SETTINGS
|
528
540
|
|
529
541
|
# The hash of attribute values. Keys are symbols with the names of the
|
530
542
|
# underlying database columns.
|
@@ -576,11 +588,10 @@ module Sequel
|
|
576
588
|
end
|
577
589
|
end
|
578
590
|
|
579
|
-
#
|
591
|
+
# Alias of eql?
|
580
592
|
def ==(obj)
|
581
|
-
(obj
|
593
|
+
eql?(obj)
|
582
594
|
end
|
583
|
-
alias eql? ==
|
584
595
|
|
585
596
|
# If pk is not nil, true only if the objects have the same class and pk.
|
586
597
|
# If pk is nil, false.
|
@@ -632,6 +643,11 @@ module Sequel
|
|
632
643
|
@values.each(&block)
|
633
644
|
end
|
634
645
|
|
646
|
+
# Compares model instances by values.
|
647
|
+
def eql?(obj)
|
648
|
+
(obj.class == model) && (obj.values == @values)
|
649
|
+
end
|
650
|
+
|
635
651
|
# Returns the validation errors associated with this object.
|
636
652
|
def errors
|
637
653
|
@errors ||= Errors.new
|
@@ -772,6 +788,13 @@ module Sequel
|
|
772
788
|
set_restricted(hash, false, except.flatten)
|
773
789
|
end
|
774
790
|
|
791
|
+
# For each of the fields in the given array +fields+, call the setter
|
792
|
+
# method with the value of that +hash+ entry for the field. Returns self.
|
793
|
+
def set_fields(hash, fields)
|
794
|
+
fields.each{|f| send("#{f}=", hash[f])}
|
795
|
+
self
|
796
|
+
end
|
797
|
+
|
775
798
|
# Set the values using the entries in the hash, only if the key
|
776
799
|
# is included in only.
|
777
800
|
def set_only(hash, *only)
|
@@ -800,6 +823,13 @@ module Sequel
|
|
800
823
|
update_restricted(hash, false, except.flatten)
|
801
824
|
end
|
802
825
|
|
826
|
+
# Update the instances values by calling +set_fields+ with the +hash+
|
827
|
+
# and +fields+, then save any changes to the record. Returns self.
|
828
|
+
def update_fields(hash, fields)
|
829
|
+
set_fields(hash, fields)
|
830
|
+
save_changes
|
831
|
+
end
|
832
|
+
|
803
833
|
# Update the values using the entries in the hash, only if the key
|
804
834
|
# is included in only.
|
805
835
|
def update_only(hash, *only)
|
@@ -808,7 +838,8 @@ module Sequel
|
|
808
838
|
|
809
839
|
# Validates the object. If the object is invalid, errors should be added
|
810
840
|
# to the errors attribute. By default, does nothing, as all models
|
811
|
-
# are valid by default.
|
841
|
+
# are valid by default. See the {"Model Validations" guide}[link:files/doc/validations_rdoc.html].
|
842
|
+
# for details about validation.
|
812
843
|
def validate
|
813
844
|
end
|
814
845
|
|
@@ -855,8 +886,10 @@ module Sequel
|
|
855
886
|
delete
|
856
887
|
end
|
857
888
|
|
889
|
+
# Insert the record into the database, returning the primary key if
|
890
|
+
# the record should be refreshed from the database.
|
858
891
|
def _insert
|
859
|
-
ds =
|
892
|
+
ds = _insert_dataset
|
860
893
|
if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
|
861
894
|
@values = h
|
862
895
|
nil
|
@@ -871,6 +904,12 @@ module Sequel
|
|
871
904
|
end
|
872
905
|
end
|
873
906
|
|
907
|
+
# The dataset to use when inserting a new object. The same as the model's
|
908
|
+
# dataset by default.
|
909
|
+
def _insert_dataset
|
910
|
+
model.dataset
|
911
|
+
end
|
912
|
+
|
874
913
|
# Refresh using a particular dataset, used inside save to make sure the same server
|
875
914
|
# is used for reading newly inserted values from the database
|
876
915
|
def _refresh(dataset)
|
@@ -886,30 +925,28 @@ module Sequel
|
|
886
925
|
if new?
|
887
926
|
return save_failure(:create) if before_create == false
|
888
927
|
pk = _insert
|
889
|
-
@this = nil
|
928
|
+
@this = nil
|
890
929
|
@new = false
|
891
930
|
@was_new = true
|
892
931
|
after_create
|
893
932
|
after_save
|
894
933
|
@was_new = nil
|
895
|
-
|
896
|
-
ds = this
|
897
|
-
ds = ds.server(:default) unless ds.opts[:server]
|
898
|
-
_refresh(ds)
|
899
|
-
else
|
900
|
-
changed_columns.clear
|
901
|
-
end
|
934
|
+
pk ? _save_refresh : changed_columns.clear
|
902
935
|
else
|
903
936
|
return save_failure(:update) if before_update == false
|
904
937
|
if columns.empty?
|
905
|
-
@columns_updated = opts[:changed]
|
938
|
+
@columns_updated = if opts[:changed]
|
939
|
+
@values.reject{|k,v| !changed_columns.include?(k)}
|
940
|
+
else
|
941
|
+
_save_update_all_columns_hash
|
942
|
+
end
|
906
943
|
changed_columns.clear
|
907
944
|
else # update only the specified columns
|
908
945
|
@columns_updated = @values.reject{|k, v| !columns.include?(k)}
|
909
946
|
changed_columns.reject!{|c| columns.include?(c)}
|
910
947
|
end
|
911
|
-
Array(primary_key).each{|x| @columns_updated.delete(x)}
|
912
948
|
_update(@columns_updated) unless @columns_updated.empty?
|
949
|
+
@this = nil
|
913
950
|
after_update
|
914
951
|
after_save
|
915
952
|
@columns_updated = nil
|
@@ -917,6 +954,25 @@ module Sequel
|
|
917
954
|
@modified = false
|
918
955
|
self
|
919
956
|
end
|
957
|
+
|
958
|
+
# Refresh the object after saving it, used to get
|
959
|
+
# default values of all columns. Separated from _save so it
|
960
|
+
# can be overridden to avoid the refresh.
|
961
|
+
def _save_refresh
|
962
|
+
_refresh(this.opts[:server] ? this : this.server(:default))
|
963
|
+
end
|
964
|
+
|
965
|
+
# Return a hash of values used when saving all columns of an
|
966
|
+
# existing object (i.e. not passing specific columns to save
|
967
|
+
# or using update/save_changes). Defaults to all of the
|
968
|
+
# object's values except unmodified primary key columns, as some
|
969
|
+
# databases don't like you setting primary key values even
|
970
|
+
# to their existing values.
|
971
|
+
def _save_update_all_columns_hash
|
972
|
+
v = @values.dup
|
973
|
+
Array(primary_key).each{|x| v.delete(x) unless changed_columns.include?(x)}
|
974
|
+
v
|
975
|
+
end
|
920
976
|
|
921
977
|
# Update this instance's dataset with the supplied column hash.
|
922
978
|
def _update(columns)
|
@@ -946,7 +1002,7 @@ module Sequel
|
|
946
1002
|
end
|
947
1003
|
|
948
1004
|
# If transactions should be used, wrap the yield in a transaction block.
|
949
|
-
def checked_transaction(opts)
|
1005
|
+
def checked_transaction(opts={})
|
950
1006
|
use_transaction?(opts) ? db.transaction(opts){yield} : yield
|
951
1007
|
end
|
952
1008
|
|