sequel 3.35.0 → 3.36.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.
- data/CHANGELOG +78 -0
- data/Rakefile +3 -3
- data/bin/sequel +3 -1
- data/doc/advanced_associations.rdoc +154 -11
- data/doc/migration.rdoc +18 -0
- data/doc/object_model.rdoc +541 -0
- data/doc/opening_databases.rdoc +4 -1
- data/doc/release_notes/3.36.0.txt +245 -0
- data/doc/schema_modification.rdoc +0 -6
- data/lib/sequel/adapters/do/mysql.rb +7 -0
- data/lib/sequel/adapters/jdbc.rb +11 -3
- data/lib/sequel/adapters/jdbc/mysql.rb +3 -5
- data/lib/sequel/adapters/jdbc/postgresql.rb +10 -8
- data/lib/sequel/adapters/jdbc/progress.rb +21 -0
- data/lib/sequel/adapters/mock.rb +2 -6
- data/lib/sequel/adapters/mysql.rb +3 -9
- data/lib/sequel/adapters/mysql2.rb +12 -11
- data/lib/sequel/adapters/postgres.rb +32 -40
- data/lib/sequel/adapters/shared/mssql.rb +15 -11
- data/lib/sequel/adapters/shared/mysql.rb +28 -3
- data/lib/sequel/adapters/shared/oracle.rb +5 -0
- data/lib/sequel/adapters/shared/postgres.rb +59 -5
- data/lib/sequel/adapters/shared/sqlite.rb +3 -13
- data/lib/sequel/adapters/sqlite.rb +0 -7
- data/lib/sequel/adapters/swift/mysql.rb +2 -5
- data/lib/sequel/adapters/tinytds.rb +1 -2
- data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
- data/lib/sequel/connection_pool/threaded.rb +9 -1
- data/lib/sequel/database/dataset_defaults.rb +3 -1
- data/lib/sequel/database/misc.rb +7 -1
- data/lib/sequel/database/query.rb +11 -3
- data/lib/sequel/database/schema_generator.rb +40 -9
- data/lib/sequel/database/schema_methods.rb +6 -1
- data/lib/sequel/dataset/actions.rb +5 -5
- data/lib/sequel/dataset/prepared_statements.rb +3 -1
- data/lib/sequel/dataset/query.rb +1 -1
- data/lib/sequel/extensions/migration.rb +28 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +0 -9
- data/lib/sequel/extensions/pg_inet.rb +89 -0
- data/lib/sequel/extensions/pg_json.rb +178 -0
- data/lib/sequel/extensions/schema_dumper.rb +24 -6
- data/lib/sequel/model/associations.rb +19 -15
- data/lib/sequel/model/base.rb +11 -12
- data/lib/sequel/plugins/composition.rb +1 -2
- data/lib/sequel/plugins/eager_each.rb +59 -0
- data/lib/sequel/plugins/json_serializer.rb +41 -4
- data/lib/sequel/plugins/nested_attributes.rb +72 -52
- data/lib/sequel/plugins/optimistic_locking.rb +8 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +271 -1
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/connection_pool_spec.rb +26 -1
- data/spec/core/database_spec.rb +19 -0
- data/spec/core/dataset_spec.rb +45 -5
- data/spec/core/expression_filters_spec.rb +31 -67
- data/spec/core/mock_adapter_spec.rb +4 -0
- data/spec/extensions/core_extensions_spec.rb +83 -0
- data/spec/extensions/eager_each_spec.rb +34 -0
- data/spec/extensions/inflector_spec.rb +0 -4
- data/spec/extensions/json_serializer_spec.rb +32 -1
- data/spec/extensions/migration_spec.rb +28 -0
- data/spec/extensions/nested_attributes_spec.rb +134 -1
- data/spec/extensions/optimistic_locking_spec.rb +15 -1
- data/spec/extensions/pg_hstore_spec.rb +1 -1
- data/spec/extensions/pg_inet_spec.rb +44 -0
- data/spec/extensions/pg_json_spec.rb +101 -0
- data/spec/extensions/prepared_statements_spec.rb +30 -0
- data/spec/extensions/rcte_tree_spec.rb +9 -0
- data/spec/extensions/schema_dumper_spec.rb +195 -7
- data/spec/extensions/serialization_spec.rb +4 -0
- data/spec/extensions/spec_helper.rb +9 -1
- data/spec/extensions/tactical_eager_loading_spec.rb +8 -0
- data/spec/integration/database_test.rb +5 -1
- data/spec/integration/prepared_statement_test.rb +20 -2
- data/spec/model/associations_spec.rb +27 -0
- data/spec/model/base_spec.rb +54 -0
- data/spec/model/model_spec.rb +6 -0
- data/spec/model/record_spec.rb +18 -0
- data/spec/rcov.opts +2 -0
- metadata +14 -3
@@ -0,0 +1,178 @@
|
|
1
|
+
# The pg_json extension adds support for Sequel to handle
|
2
|
+
# PostgreSQL's json type. It is slightly more strict than the
|
3
|
+
# PostgreSQL json type in that the object returned must be an
|
4
|
+
# array or object (PostgreSQL's json type considers plain numbers
|
5
|
+
# and strings as valid). This is because Sequel relies completely
|
6
|
+
# on the ruby JSON library for parsing, and ruby's JSON library
|
7
|
+
# does not accept the values.
|
8
|
+
#
|
9
|
+
# This extension integrates with Sequel's native postgres adapter, so
|
10
|
+
# that when json fields are retrieved, they are parsed and returned
|
11
|
+
# as instances of Sequel::Postgres::JSONArray or
|
12
|
+
# Sequel::Postgres::JSONHash. JSONArray and JSONHash are
|
13
|
+
# DelegateClasses of Array and Hash, so they mostly act the same, but
|
14
|
+
# not completely (json_array.is_a?(Array) is false). If you want
|
15
|
+
# the actual array for a JSONArray, call JSONArray#to_a. If you want
|
16
|
+
# the actual hash for a JSONHash, call JSONHash#to_hash.
|
17
|
+
# This is done so that Sequel does not treat JSONArray and JSONHash
|
18
|
+
# like Array and Hash by default, which would cause issues.
|
19
|
+
#
|
20
|
+
# To turn an existing Array or Hash into a JSONArray or JSONHash:
|
21
|
+
#
|
22
|
+
# array.pg_json
|
23
|
+
# hash.pg_json
|
24
|
+
#
|
25
|
+
# So if you want to insert an array or hash into an json database column:
|
26
|
+
#
|
27
|
+
# DB[:table].insert(:column=>[1, 2, 3].pg_json)
|
28
|
+
# DB[:table].insert(:column=>{'a'=>1, 'b'=>2}.pg_json)
|
29
|
+
#
|
30
|
+
# If you would like to use PostgreSQL json columns in your model
|
31
|
+
# objects, you probably want to modify the schema parsing/typecasting
|
32
|
+
# so that it recognizes and correctly handles the json type, which
|
33
|
+
# you can do by:
|
34
|
+
#
|
35
|
+
# DB.extend Sequel::Postgres::JSONDatabaseMethods
|
36
|
+
#
|
37
|
+
# If you are not using the native postgres adapter, you probably
|
38
|
+
# also want to use the typecast_on_load plugin in the model, and
|
39
|
+
# set it to typecast the json column(s) on load.
|
40
|
+
#
|
41
|
+
# The extension is designed to be used with the json type natively
|
42
|
+
# supported by PostgreSQL 9.2+. There was also a PostgreSQL extension released
|
43
|
+
# that allowed the json type to be used on PostgreSQL 9.1. To make
|
44
|
+
# this extension support that type in the native adapter, do the
|
45
|
+
# following after loading this extension:
|
46
|
+
#
|
47
|
+
# Sequel::Postgres::PG_NAMED_TYPES = {} unless defined?(Sequel::Postgres::PG_NAMED_TYPES)
|
48
|
+
# Sequel::Postgres::PG_NAMED_TYPES[:json] = Sequel::Postgres::PG_TYPES[114]
|
49
|
+
#
|
50
|
+
# This extension requires both the json and delegate libraries.
|
51
|
+
|
52
|
+
require 'delegate'
|
53
|
+
require 'json'
|
54
|
+
|
55
|
+
module Sequel
|
56
|
+
module Postgres
|
57
|
+
CAST_JSON = '::json'.freeze
|
58
|
+
|
59
|
+
# Class representating PostgreSQL JSON column array values.
|
60
|
+
class JSONArray < DelegateClass(Array)
|
61
|
+
# Convert the array to a string using to_json, append a
|
62
|
+
# literalized version of the string to the sql, and explicitly
|
63
|
+
# cast the string to json.
|
64
|
+
def sql_literal_append(ds, sql)
|
65
|
+
ds.literal_append(sql, to_json)
|
66
|
+
sql << CAST_JSON
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Class representating PostgreSQL JSON column hash/object values.
|
71
|
+
class JSONHash < DelegateClass(Hash)
|
72
|
+
# Convert the array to a string using to_json, append a
|
73
|
+
# literalized version of the string to the sql, and explicitly
|
74
|
+
# cast the string to json.
|
75
|
+
def sql_literal_append(ds, sql)
|
76
|
+
ds.literal_append(sql, to_json)
|
77
|
+
sql << CAST_JSON
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return the object being delegated to.
|
81
|
+
alias to_hash __getobj__
|
82
|
+
end
|
83
|
+
|
84
|
+
# Methods enabling Database object integration with the json type.
|
85
|
+
module JSONDatabaseMethods
|
86
|
+
# Parse the given string as json, returning either a JSONArray
|
87
|
+
# or JSONHash instance, and raising an error if the JSON
|
88
|
+
# parsing does not yield an array or hash.
|
89
|
+
def self.parse_json(s)
|
90
|
+
begin
|
91
|
+
value = JSON.parse(s)
|
92
|
+
rescue JSON::ParserError=>e
|
93
|
+
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
94
|
+
end
|
95
|
+
|
96
|
+
case value
|
97
|
+
when Array
|
98
|
+
JSONArray.new(value)
|
99
|
+
when Hash
|
100
|
+
JSONHash.new(value)
|
101
|
+
else
|
102
|
+
raise Sequel::InvalidValue, "unhandled json value: #{value.inspect} (from #{s.inspect})"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Reset the conversion procs when extending the Database object, so
|
107
|
+
# it will pick up the json convertor. This is only done for the native
|
108
|
+
# postgres adapter.
|
109
|
+
def self.extended(db)
|
110
|
+
db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Handle JSONArray and JSONHash in bound variables
|
114
|
+
def bound_variable_arg(arg, conn)
|
115
|
+
case arg
|
116
|
+
when JSONArray, JSONHash
|
117
|
+
arg.to_json
|
118
|
+
else
|
119
|
+
super
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Make the column type detection recognize the json type.
|
124
|
+
def schema_column_type(db_type)
|
125
|
+
case db_type
|
126
|
+
when 'json'
|
127
|
+
:json
|
128
|
+
else
|
129
|
+
super
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
# Given a value to typecast to the json column
|
136
|
+
# * If given a JSONArray or JSONHash, just return the value
|
137
|
+
# * If given an Array, return a JSONArray
|
138
|
+
# * If given a Hash, return a JSONHash
|
139
|
+
# * If given a String, parse it as would be done during
|
140
|
+
# database retrieval.
|
141
|
+
def typecast_value_json(value)
|
142
|
+
case value
|
143
|
+
when JSONArray, JSONHash
|
144
|
+
value
|
145
|
+
when Array
|
146
|
+
JSONArray.new(value)
|
147
|
+
when Hash
|
148
|
+
JSONHash.new(value)
|
149
|
+
when String
|
150
|
+
JSONDatabaseMethods.parse_json(value)
|
151
|
+
else
|
152
|
+
raise Sequel::InvalidValue, "invalid value for json: #{value.inspect}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
PG_TYPES = {} unless defined?(PG_TYPES)
|
158
|
+
PG_TYPES[114] = JSONDatabaseMethods.method(:parse_json)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class Array
|
163
|
+
# Return a Sequel::Postgres::JSONArray proxy to the receiver.
|
164
|
+
# This is mostly useful as a short cut for creating JSONArray
|
165
|
+
# objects that didn't come from the database.
|
166
|
+
def pg_json
|
167
|
+
Sequel::Postgres::JSONArray.new(self)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class Hash
|
172
|
+
# Return a Sequel::Postgres::JSONHash proxy to the receiver.
|
173
|
+
# This is mostly useful as a short cut for creating JSONHash
|
174
|
+
# objects that didn't come from the database.
|
175
|
+
def pg_json
|
176
|
+
Sequel::Postgres::JSONHash.new(self)
|
177
|
+
end
|
178
|
+
end
|
@@ -28,6 +28,9 @@ END_MIG
|
|
28
28
|
# the :indexes=>false option to dump_schema_migration. Options:
|
29
29
|
# * :same_db - Create a dump for the same database type, so
|
30
30
|
# don't ignore errors if the index statements fail.
|
31
|
+
# * :index_names - If set to false, don't record names of indexes. If
|
32
|
+
# set to :namespace, prepend the table name to the index name if the
|
33
|
+
# database does not use a global index namespace.
|
31
34
|
def dump_indexes_migration(options={})
|
32
35
|
ts = tables(options)
|
33
36
|
<<END_MIG
|
@@ -53,6 +56,8 @@ END_MIG
|
|
53
56
|
# * :foreign_keys - If set to false, don't dump foreign_keys
|
54
57
|
# * :indexes - If set to false, don't dump indexes (they can be added
|
55
58
|
# later via dump_index_migration).
|
59
|
+
# * :index_names - If set to false, don't record names of indexes. If
|
60
|
+
# set to :namespace, prepend the table name to the index name.
|
56
61
|
def dump_schema_migration(options={})
|
57
62
|
options = options.dup
|
58
63
|
if options[:indexes] == false && !options.has_key?(:foreign_keys)
|
@@ -144,8 +149,14 @@ END_MIG
|
|
144
149
|
# database type is not recognized, return it as a String type.
|
145
150
|
def column_schema_to_ruby_type(schema)
|
146
151
|
case t = schema[:db_type].downcase
|
147
|
-
when /\A(
|
148
|
-
|
152
|
+
when /\A(medium|small)?int(?:eger)?(?:\((\d+)\))?( unsigned)?\z/o
|
153
|
+
if !$1 && $2 && $2.to_i >= 10 && $3
|
154
|
+
# Unsigned integer type with 10 digits can potentially contain values which
|
155
|
+
# don't fit signed integer type, so use bigint type in target database.
|
156
|
+
{:type=>Bignum}
|
157
|
+
else
|
158
|
+
{:type=>Integer}
|
159
|
+
end
|
149
160
|
when /\Atinyint(?:\((\d+)\))?(?: unsigned)?\z/o
|
150
161
|
{:type =>schema[:type] == :boolean ? TrueClass : Integer}
|
151
162
|
when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/o
|
@@ -261,7 +272,7 @@ END_MIG
|
|
261
272
|
Schema::Generator.new(self) do
|
262
273
|
s.each{|name, info| send(*m.call(name, info, options))}
|
263
274
|
primary_key(pks) if !@primary_key && pks.length > 0
|
264
|
-
indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts))} if indexes
|
275
|
+
indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts, options))} if indexes
|
265
276
|
composite_fks.each{|fk| send(:foreign_key, fk[:columns], fk)} if composite_fks
|
266
277
|
end
|
267
278
|
end
|
@@ -276,15 +287,21 @@ END_MIG
|
|
276
287
|
end
|
277
288
|
im = method(:index_to_generator_opts)
|
278
289
|
gen = Schema::Generator.new(self) do
|
279
|
-
indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts))}
|
290
|
+
indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts, options))}
|
280
291
|
end
|
281
292
|
gen.dump_indexes(meth=>table, :ignore_errors=>!options[:same_db])
|
282
293
|
end
|
283
294
|
|
284
295
|
# Convert the parsed index information into options to the Generators index method.
|
285
|
-
def index_to_generator_opts(table, name, index_opts)
|
296
|
+
def index_to_generator_opts(table, name, index_opts, options={})
|
286
297
|
h = {}
|
287
|
-
|
298
|
+
if options[:index_names] != false && default_index_name(table, index_opts[:columns]) != name.to_s
|
299
|
+
if options[:index_names] == :namespace && !global_index_namespace?
|
300
|
+
h[:name] = "#{table}_#{name}".to_sym
|
301
|
+
else
|
302
|
+
h[:name] = name
|
303
|
+
end
|
304
|
+
end
|
288
305
|
h[:unique] = true if index_opts[:unique]
|
289
306
|
h
|
290
307
|
end
|
@@ -444,6 +461,7 @@ END_MIG
|
|
444
461
|
"index #{cols.inspect}#{opts_inspect(c)}"
|
445
462
|
end
|
446
463
|
end
|
464
|
+
is = is.reverse if options[:drop_index]
|
447
465
|
is.join("\n")
|
448
466
|
end
|
449
467
|
|
@@ -1644,32 +1644,36 @@ module Sequel
|
|
1644
1644
|
end
|
1645
1645
|
end
|
1646
1646
|
|
1647
|
-
# Set the given object as the associated object for the given
|
1648
|
-
def
|
1649
|
-
|
1647
|
+
# Set the given object as the associated object for the given *_to_one association reflection
|
1648
|
+
def _set_associated_object(opts, o)
|
1649
|
+
a = associations[opts[:name]]
|
1650
|
+
return if a && a == o && !set_associated_object_if_same?
|
1650
1651
|
run_association_callbacks(opts, :before_set, o)
|
1651
|
-
|
1652
|
-
remove_reciprocal_object(opts, a)
|
1653
|
-
end
|
1652
|
+
remove_reciprocal_object(opts, a) if a
|
1654
1653
|
send(opts._setter_method, o)
|
1655
1654
|
associations[opts[:name]] = o
|
1656
1655
|
add_reciprocal_object(opts, o) if o
|
1657
1656
|
run_association_callbacks(opts, :after_set, o)
|
1658
1657
|
o
|
1659
1658
|
end
|
1659
|
+
|
1660
|
+
# Whether run the associated object setter code if passed the same object as the one already
|
1661
|
+
# cached in the association. Usually not set (so nil), can be set on a per-object basis
|
1662
|
+
# if necessary.
|
1663
|
+
def set_associated_object_if_same?
|
1664
|
+
@set_associated_object_if_same
|
1665
|
+
end
|
1660
1666
|
|
1667
|
+
# Set the given object as the associated object for the given many_to_one association reflection
|
1668
|
+
def set_associated_object(opts, o)
|
1669
|
+
raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk
|
1670
|
+
_set_associated_object(opts, o)
|
1671
|
+
end
|
1672
|
+
|
1661
1673
|
# Set the given object as the associated object for the given one_to_one association reflection
|
1662
1674
|
def set_one_to_one_associated_object(opts, o)
|
1663
1675
|
raise(Error, "object #{inspect} does not have a primary key") unless pk
|
1664
|
-
|
1665
|
-
if a = associations[opts[:name]]
|
1666
|
-
remove_reciprocal_object(opts, a)
|
1667
|
-
end
|
1668
|
-
send(opts._setter_method, o)
|
1669
|
-
associations[opts[:name]] = o
|
1670
|
-
add_reciprocal_object(opts, o) if o
|
1671
|
-
run_association_callbacks(opts, :after_set, o)
|
1672
|
-
o
|
1676
|
+
_set_associated_object(opts, o)
|
1673
1677
|
end
|
1674
1678
|
end
|
1675
1679
|
|
data/lib/sequel/model/base.rb
CHANGED
@@ -350,16 +350,14 @@ module Sequel
|
|
350
350
|
subclass.instance_variable_set(iv, sup_class_value)
|
351
351
|
end
|
352
352
|
unless ivs.include?("@dataset")
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
subclass.set_dataset(subclass.implicit_table_name)
|
358
|
-
elsif @dataset
|
359
|
-
subclass.set_dataset(@dataset.clone, :inherited=>true)
|
353
|
+
if self == Model || !@dataset
|
354
|
+
n = subclass.name
|
355
|
+
unless n.nil? || n.empty?
|
356
|
+
db
|
357
|
+
subclass.set_dataset(subclass.implicit_table_name) rescue nil
|
360
358
|
end
|
361
|
-
|
362
|
-
nil
|
359
|
+
elsif @dataset
|
360
|
+
subclass.set_dataset(@dataset.clone, :inherited=>true) rescue nil
|
363
361
|
end
|
364
362
|
end
|
365
363
|
end
|
@@ -1106,7 +1104,8 @@ module Sequel
|
|
1106
1104
|
# a.update(...)
|
1107
1105
|
# end
|
1108
1106
|
def lock!
|
1109
|
-
|
1107
|
+
_refresh(this.for_update) unless new?
|
1108
|
+
self
|
1110
1109
|
end
|
1111
1110
|
|
1112
1111
|
# Remove elements of the model object that make marshalling fail. Returns self.
|
@@ -1185,6 +1184,7 @@ module Sequel
|
|
1185
1184
|
def refresh
|
1186
1185
|
raise Sequel::Error, "can't refresh frozen object" if frozen?
|
1187
1186
|
_refresh(this)
|
1187
|
+
self
|
1188
1188
|
end
|
1189
1189
|
|
1190
1190
|
# Alias of refresh, but not aliased directly to make overriding in a plugin easier.
|
@@ -1532,7 +1532,6 @@ module Sequel
|
|
1532
1532
|
def _refresh(dataset)
|
1533
1533
|
set_values(_refresh_get(dataset) || raise(Error, "Record not found"))
|
1534
1534
|
changed_columns.clear
|
1535
|
-
self
|
1536
1535
|
end
|
1537
1536
|
|
1538
1537
|
# Get the row of column data from the database.
|
@@ -1809,7 +1808,7 @@ module Sequel
|
|
1809
1808
|
# for database specific column types.
|
1810
1809
|
def typecast_value(column, value)
|
1811
1810
|
return value unless typecast_on_assignment && db_schema && (col_schema = db_schema[column])
|
1812
|
-
value = nil if
|
1811
|
+
value = nil if '' == value and typecast_empty_string_to_nil and col_schema[:type] and ![:string, :blob].include?(col_schema[:type])
|
1813
1812
|
raise(InvalidValue, "nil/NULL is not allowed for the #{column} column") if raise_on_typecast_failure && value.nil? && (col_schema[:allow_null] == false)
|
1814
1813
|
begin
|
1815
1814
|
model.db.typecast_value(col_schema[:type], value)
|
@@ -150,9 +150,8 @@ module Sequel
|
|
150
150
|
module InstanceMethods
|
151
151
|
# Clear the cached compositions when refreshing.
|
152
152
|
def _refresh(ds)
|
153
|
-
|
153
|
+
super
|
154
154
|
compositions.clear
|
155
|
-
v
|
156
155
|
end
|
157
156
|
|
158
157
|
# For each composition, set the columns in the model class based
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The eager_each plugin makes calling each on an eager loaded dataset do eager loading.
|
4
|
+
# By default, each does not work on an eager loaded dataset, because each iterates
|
5
|
+
# over rows of the dataset as they come in, and to eagerly load you need to have all
|
6
|
+
# values up front. With the default associations code, you must call #all on an eagerly
|
7
|
+
# loaded dataset, as calling #each on an #eager dataset skips the eager loading, and calling
|
8
|
+
# #each on an #eager_graph dataset makes it yield plain hashes with columns from all
|
9
|
+
# tables, instead of yielding the instances of the main model.
|
10
|
+
#
|
11
|
+
# This plugin makes #each call #all for eagerly loaded datasets. As #all usually calls
|
12
|
+
# #each, this is a bit of issue, but this plugin resolves the issue by cloning the dataset
|
13
|
+
# and setting a new flag in the cloned dataset, so that each can check with the flag to
|
14
|
+
# determine whether it should call all.
|
15
|
+
#
|
16
|
+
# Usage:
|
17
|
+
#
|
18
|
+
# # Make all model subclass instances eagerly load for each (called before loading subclasses)
|
19
|
+
# Sequel::Model.plugin :eager_each
|
20
|
+
#
|
21
|
+
# # Make the Album class eagerly load for each
|
22
|
+
# Album.plugin :eager_each
|
23
|
+
module EagerEach
|
24
|
+
# Methods added to eagerly loaded datasets when the eager_each plugin is in use.
|
25
|
+
module EagerDatasetMethods
|
26
|
+
# Call #all instead of #each unless #each is being called by #all.
|
27
|
+
def each(&block)
|
28
|
+
if opts[:all_called]
|
29
|
+
super
|
30
|
+
else
|
31
|
+
all(&block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Clone the dataset and set a flag to let #each know not to call #all,
|
36
|
+
# to avoid the infinite loop.
|
37
|
+
def all(&block)
|
38
|
+
if opts[:all_called]
|
39
|
+
super
|
40
|
+
else
|
41
|
+
clone(:all_called=>true).all(&block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module DatasetMethods
|
47
|
+
# Make sure calling each on this dataset will eagerly load the dataset.
|
48
|
+
def eager(*)
|
49
|
+
super.extend(EagerDatasetMethods)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Make sure calling each on this dataset will eagerly load the dataset.
|
53
|
+
def eager_graph(*)
|
54
|
+
super.extend(EagerDatasetMethods)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -139,8 +139,13 @@ module Sequel
|
|
139
139
|
module InstanceMethods
|
140
140
|
# Parse the provided JSON, which should return a hash,
|
141
141
|
# and call +set+ with that hash.
|
142
|
-
def from_json(json)
|
143
|
-
|
142
|
+
def from_json(json, opts={})
|
143
|
+
h = JSON.parse(json)
|
144
|
+
if fields = opts[:fields]
|
145
|
+
set_fields(h, fields, opts)
|
146
|
+
else
|
147
|
+
set(h)
|
148
|
+
end
|
144
149
|
end
|
145
150
|
|
146
151
|
# Return a string in JSON format. Accepts the following
|
@@ -200,7 +205,21 @@ module Sequel
|
|
200
205
|
module DatasetMethods
|
201
206
|
# Return a JSON string representing an array of all objects in
|
202
207
|
# this dataset. Takes the same options as the the instance
|
203
|
-
# method, and passes them to every instance.
|
208
|
+
# method, and passes them to every instance. Additionally,
|
209
|
+
# respects the following options:
|
210
|
+
#
|
211
|
+
# :array :: An array of instances. If this is not provided,
|
212
|
+
# calls #all on the receiver to get the array.
|
213
|
+
# :root :: If set to :collection, only wraps the collection
|
214
|
+
# in a root object. If set to :instance, only wraps
|
215
|
+
# the instances in a root object. If set to :both,
|
216
|
+
# wraps both the collection and instances in a root
|
217
|
+
# object. Unfortunately, for backwards compatibility,
|
218
|
+
# if this option is true and doesn't match one of those
|
219
|
+
# symbols, it defaults to both. That may change in a
|
220
|
+
# future version, so for forwards compatibility, you
|
221
|
+
# should pick a specific symbol for your desired
|
222
|
+
# behavior.
|
204
223
|
def to_json(*a)
|
205
224
|
if opts = a.first.is_a?(Hash)
|
206
225
|
opts = model.json_serializer_opts.merge(a.first)
|
@@ -208,6 +227,19 @@ module Sequel
|
|
208
227
|
else
|
209
228
|
opts = model.json_serializer_opts
|
210
229
|
end
|
230
|
+
|
231
|
+
collection_root = case opts[:root]
|
232
|
+
when nil, false, :instance
|
233
|
+
false
|
234
|
+
when :collection
|
235
|
+
opts = opts.dup
|
236
|
+
opts.delete(:root)
|
237
|
+
opts[:naked] = true unless opts.has_key?(:naked)
|
238
|
+
true
|
239
|
+
else
|
240
|
+
true
|
241
|
+
end
|
242
|
+
|
211
243
|
res = if row_proc
|
212
244
|
array = if opts[:array]
|
213
245
|
opts = opts.dup
|
@@ -219,7 +251,12 @@ module Sequel
|
|
219
251
|
else
|
220
252
|
all
|
221
253
|
end
|
222
|
-
|
254
|
+
|
255
|
+
if collection_root
|
256
|
+
{model.send(:pluralize, model.send(:underscore, model.to_s)) => res}.to_json(*a)
|
257
|
+
else
|
258
|
+
res.to_json(*a)
|
259
|
+
end
|
223
260
|
end
|
224
261
|
end
|
225
262
|
end
|