sequel 3.37.0 → 3.38.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 +56 -0
- data/README.rdoc +82 -58
- data/Rakefile +6 -5
- data/bin/sequel +1 -1
- data/doc/active_record.rdoc +67 -52
- data/doc/advanced_associations.rdoc +33 -48
- data/doc/association_basics.rdoc +41 -51
- data/doc/cheat_sheet.rdoc +21 -21
- data/doc/core_extensions.rdoc +374 -0
- data/doc/dataset_basics.rdoc +5 -5
- data/doc/dataset_filtering.rdoc +47 -43
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +4 -5
- data/doc/model_hooks.rdoc +3 -3
- data/doc/object_model.rdoc +31 -25
- data/doc/opening_databases.rdoc +19 -5
- data/doc/prepared_statements.rdoc +2 -2
- data/doc/querying.rdoc +109 -52
- data/doc/reflection.rdoc +6 -6
- data/doc/release_notes/3.38.0.txt +234 -0
- data/doc/schema_modification.rdoc +22 -13
- data/doc/sharding.rdoc +8 -9
- data/doc/sql.rdoc +154 -112
- data/doc/testing.rdoc +47 -7
- data/doc/thread_safety.rdoc +1 -1
- data/doc/transactions.rdoc +1 -1
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +29 -43
- data/lib/sequel/adapters/do/postgres.rb +1 -4
- data/lib/sequel/adapters/jdbc.rb +14 -3
- data/lib/sequel/adapters/jdbc/db2.rb +9 -0
- data/lib/sequel/adapters/jdbc/derby.rb +41 -4
- data/lib/sequel/adapters/jdbc/jtds.rb +11 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -6
- data/lib/sequel/adapters/mock.rb +10 -4
- data/lib/sequel/adapters/postgres.rb +1 -28
- data/lib/sequel/adapters/shared/mssql.rb +23 -13
- data/lib/sequel/adapters/shared/postgres.rb +46 -0
- data/lib/sequel/adapters/swift.rb +21 -13
- data/lib/sequel/adapters/swift/mysql.rb +1 -0
- data/lib/sequel/adapters/swift/postgres.rb +4 -5
- data/lib/sequel/adapters/swift/sqlite.rb +2 -1
- data/lib/sequel/adapters/tinytds.rb +14 -2
- data/lib/sequel/adapters/utils/pg_types.rb +5 -0
- data/lib/sequel/core.rb +29 -17
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +3 -0
- data/lib/sequel/dataset/actions.rb +5 -6
- data/lib/sequel/dataset/query.rb +7 -7
- data/lib/sequel/dataset/sql.rb +5 -18
- data/lib/sequel/extensions/core_extensions.rb +8 -12
- data/lib/sequel/extensions/pg_array.rb +59 -33
- data/lib/sequel/extensions/pg_array_ops.rb +32 -4
- data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -1
- data/lib/sequel/extensions/pg_hstore.rb +32 -17
- data/lib/sequel/extensions/pg_hstore_ops.rb +32 -3
- data/lib/sequel/extensions/pg_inet.rb +1 -2
- data/lib/sequel/extensions/pg_interval.rb +0 -1
- data/lib/sequel/extensions/pg_json.rb +41 -23
- data/lib/sequel/extensions/pg_range.rb +36 -11
- data/lib/sequel/extensions/pg_range_ops.rb +32 -4
- data/lib/sequel/extensions/pg_row.rb +572 -0
- data/lib/sequel/extensions/pg_row_ops.rb +164 -0
- data/lib/sequel/extensions/query.rb +3 -3
- data/lib/sequel/extensions/schema_dumper.rb +7 -8
- data/lib/sequel/extensions/select_remove.rb +1 -1
- data/lib/sequel/model/base.rb +1 -0
- data/lib/sequel/no_core_ext.rb +1 -1
- data/lib/sequel/plugins/pg_row.rb +121 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
- data/lib/sequel/plugins/validation_helpers.rb +31 -0
- data/lib/sequel/sql.rb +64 -44
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +37 -12
- data/spec/adapters/mysql_spec.rb +39 -75
- data/spec/adapters/oracle_spec.rb +11 -11
- data/spec/adapters/postgres_spec.rb +414 -237
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +14 -14
- data/spec/core/database_spec.rb +6 -6
- data/spec/core/dataset_spec.rb +169 -205
- data/spec/core/expression_filters_spec.rb +182 -295
- data/spec/core/object_graph_spec.rb +6 -6
- data/spec/core/schema_spec.rb +14 -14
- data/spec/core/spec_helper.rb +1 -0
- data/spec/{extensions/core_extensions_spec.rb → core_extensions_spec.rb} +208 -14
- data/spec/extensions/columns_introspection_spec.rb +5 -5
- data/spec/extensions/hook_class_methods_spec.rb +28 -36
- data/spec/extensions/many_through_many_spec.rb +4 -4
- data/spec/extensions/pg_array_ops_spec.rb +15 -7
- data/spec/extensions/pg_array_spec.rb +81 -48
- data/spec/extensions/pg_auto_parameterize_spec.rb +2 -2
- data/spec/extensions/pg_hstore_ops_spec.rb +13 -9
- data/spec/extensions/pg_hstore_spec.rb +66 -65
- data/spec/extensions/pg_inet_spec.rb +2 -4
- data/spec/extensions/pg_interval_spec.rb +2 -3
- data/spec/extensions/pg_json_spec.rb +20 -18
- data/spec/extensions/pg_range_ops_spec.rb +11 -4
- data/spec/extensions/pg_range_spec.rb +30 -7
- data/spec/extensions/pg_row_ops_spec.rb +48 -0
- data/spec/extensions/pg_row_plugin_spec.rb +45 -0
- data/spec/extensions/pg_row_spec.rb +323 -0
- data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
- data/spec/extensions/query_literals_spec.rb +11 -11
- data/spec/extensions/query_spec.rb +3 -3
- data/spec/extensions/schema_dumper_spec.rb +20 -4
- data/spec/extensions/schema_spec.rb +18 -41
- data/spec/extensions/select_remove_spec.rb +4 -4
- data/spec/extensions/spec_helper.rb +4 -8
- data/spec/extensions/to_dot_spec.rb +5 -5
- data/spec/extensions/validation_class_methods_spec.rb +28 -16
- data/spec/integration/associations_test.rb +20 -20
- data/spec/integration/dataset_test.rb +98 -98
- data/spec/integration/eager_loader_test.rb +13 -27
- data/spec/integration/plugin_test.rb +5 -5
- data/spec/integration/prepared_statement_test.rb +22 -13
- data/spec/integration/schema_test.rb +28 -18
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/timezone_test.rb +2 -2
- data/spec/integration/type_test.rb +15 -6
- data/spec/model/association_reflection_spec.rb +1 -1
- data/spec/model/associations_spec.rb +4 -4
- data/spec/model/base_spec.rb +5 -5
- data/spec/model/eager_loading_spec.rb +15 -15
- data/spec/model/model_spec.rb +32 -32
- data/spec/model/record_spec.rb +16 -0
- data/spec/model/spec_helper.rb +2 -6
- data/spec/model/validations_spec.rb +1 -1
- metadata +16 -4
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# The pg_row_ops extension adds support to Sequel's DSL to make
|
|
2
|
+
# it easier to deal with PostgreSQL row-valued/composite types.
|
|
3
|
+
#
|
|
4
|
+
# To load the extension:
|
|
5
|
+
#
|
|
6
|
+
# Sequel.extension :pg_row_ops
|
|
7
|
+
#
|
|
8
|
+
# The most common usage is passing an expression to Sequel.pg_row_op:
|
|
9
|
+
#
|
|
10
|
+
# r = Sequel.pg_row_op(:row_column)
|
|
11
|
+
#
|
|
12
|
+
# If you have also loaded the pg_row extension, you can use
|
|
13
|
+
# Sequel.pg_row as well:
|
|
14
|
+
#
|
|
15
|
+
# r = Sequel.pg_row(:row_column)
|
|
16
|
+
#
|
|
17
|
+
# Also, on most Sequel expression objects, you can call the pg_row
|
|
18
|
+
# method:
|
|
19
|
+
#
|
|
20
|
+
# r = Sequel.expr(:row_column).pg_row
|
|
21
|
+
#
|
|
22
|
+
# If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
|
|
23
|
+
# you can also call Symbol#pg_row:
|
|
24
|
+
#
|
|
25
|
+
# r = :row_column.pg_row
|
|
26
|
+
#
|
|
27
|
+
# There's only fairly basic support currently. You can use the [] method to access
|
|
28
|
+
# a member of the composite type:
|
|
29
|
+
#
|
|
30
|
+
# r[:a] # (row_column).a
|
|
31
|
+
#
|
|
32
|
+
# This can be chained:
|
|
33
|
+
#
|
|
34
|
+
# r[:a][:b] # ((row_column).a).b
|
|
35
|
+
#
|
|
36
|
+
# If you've loaded the pg_array_ops extension, you there is also support for composite
|
|
37
|
+
# types that include arrays, or arrays of composite types:
|
|
38
|
+
#
|
|
39
|
+
# r[1][:a] # (row_column[1]).a
|
|
40
|
+
# r[:a][1] # (row_column).a[1]
|
|
41
|
+
#
|
|
42
|
+
# The only other support is the splat method:
|
|
43
|
+
#
|
|
44
|
+
# r.splat # (row_column.*)
|
|
45
|
+
#
|
|
46
|
+
# The splat method is necessary if you are trying to reference a table's type when the
|
|
47
|
+
# table has the same name as one of it's columns. For example:
|
|
48
|
+
#
|
|
49
|
+
# DB.create_table(:a){Integer :a; Integer :b}
|
|
50
|
+
#
|
|
51
|
+
# Let's say you want to reference the composite type for the table:
|
|
52
|
+
#
|
|
53
|
+
# a = Sequel.pg_row_op(:a)
|
|
54
|
+
# DB[:a].select(a[:b]) # SELECT (a).b FROM a
|
|
55
|
+
#
|
|
56
|
+
# Unfortunately, that doesn't work, as it references the integer column, not the table.
|
|
57
|
+
# The splat method works around this:
|
|
58
|
+
#
|
|
59
|
+
# DB[:a].select(a.splat[:b]) # SELECT (a.*).b FROM a
|
|
60
|
+
#
|
|
61
|
+
# Splat also takes an argument which is used for casting. This is necessary if you
|
|
62
|
+
# want to return the composite type itself, instead of the columns in the composite
|
|
63
|
+
# type. For example:
|
|
64
|
+
#
|
|
65
|
+
# DB[:a].select(a.splat).first # SELECT (a.*) FROM a
|
|
66
|
+
# # => {:a=>1, :b=>2}
|
|
67
|
+
#
|
|
68
|
+
# By casting the expression, you can get a composite type returned:
|
|
69
|
+
#
|
|
70
|
+
# DB[:a].select(a.splat).first # SELECT (a.*)::a FROM a
|
|
71
|
+
# # => {:a=>"(1,2)"} # or {:a=>{:a=>1, :b=>2}} if the "a" type has been registered
|
|
72
|
+
# # with the the pg_row extension
|
|
73
|
+
#
|
|
74
|
+
# This feature is mostly useful for a different way to graph tables:
|
|
75
|
+
#
|
|
76
|
+
# DB[:a].join(:b, :id=>:b_id).select(Sequel.pg_row_op(:a).splat(:a),
|
|
77
|
+
# Sequel.pg_row_op(:b).splat(:b))
|
|
78
|
+
# # SELECT (a.*)::a, (b.*)::b FROM a INNER JOIN b ON (b.id = a.b_id)
|
|
79
|
+
# # => {:a=>{:id=>1, :b_id=>2}, :b=>{:id=>2}}
|
|
80
|
+
|
|
81
|
+
module Sequel
|
|
82
|
+
module Postgres
|
|
83
|
+
# This class represents a composite type expression reference.
|
|
84
|
+
class PGRowOp < SQL::PlaceholderLiteralString
|
|
85
|
+
OPEN = '('.freeze
|
|
86
|
+
CLOSE_DOT = ').'.freeze
|
|
87
|
+
CLOSE_STAR = '.*)'.freeze
|
|
88
|
+
CLOSE_STAR_CAST = '.*)::'.freeze
|
|
89
|
+
EMPTY = "".freeze
|
|
90
|
+
ROW = [OPEN, CLOSE_STAR].freeze
|
|
91
|
+
ROW_CAST = [OPEN, CLOSE_STAR_CAST].freeze
|
|
92
|
+
QUALIFY = [OPEN, CLOSE_DOT].freeze
|
|
93
|
+
WRAP = [EMPTY].freeze
|
|
94
|
+
|
|
95
|
+
# Wrap the expression in a PGRowOp, without changing the
|
|
96
|
+
# SQL it would use.
|
|
97
|
+
def self.wrap(expr)
|
|
98
|
+
PGRowOp.new(WRAP, [expr])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Access a member of the composite type if given a
|
|
102
|
+
# symbol or an SQL::Identifier. For all other access,
|
|
103
|
+
# assuming the pg_array_ops extension is loaded and
|
|
104
|
+
# that it represents an array access. In either
|
|
105
|
+
# case, return a PgRowOp so that access can be cascaded.
|
|
106
|
+
def [](member)
|
|
107
|
+
case member
|
|
108
|
+
when Symbol, SQL::Identifier
|
|
109
|
+
PGRowOp.new(QUALIFY, [self, member])
|
|
110
|
+
else
|
|
111
|
+
PGRowOp.wrap(Sequel.pg_array_op(self)[member])
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Use the (identifier.*) syntax to indicate that this
|
|
116
|
+
# expression represents the composite type of one
|
|
117
|
+
# of the tables being referenced, if it has the same
|
|
118
|
+
# name as one of the columns. If the cast_to argument
|
|
119
|
+
# is given, also cast the expression to that type
|
|
120
|
+
# (which should be a symbol representing the composite type).
|
|
121
|
+
# This is used if you want to return whole table row as a
|
|
122
|
+
# composite type.
|
|
123
|
+
def splat(cast_to=nil)
|
|
124
|
+
if args.length > 1
|
|
125
|
+
raise Error, 'cannot splat a PGRowOp with multiple arguments'
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if cast_to
|
|
129
|
+
PGRowOp.new(ROW_CAST, args + [cast_to])
|
|
130
|
+
else
|
|
131
|
+
PGRowOp.new(ROW, args)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
module ExpressionMethods
|
|
136
|
+
# Return a PGRowOp wrapping the receiver.
|
|
137
|
+
def pg_row
|
|
138
|
+
Sequel.pg_row_op(self)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
module SQL::Builders
|
|
145
|
+
# Return a PGRowOp wrapping the given expression.
|
|
146
|
+
def pg_row_op(expr)
|
|
147
|
+
Postgres::PGRowOp.wrap(expr)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class SQL::GenericExpression
|
|
152
|
+
include Sequel::Postgres::PGRowOp::ExpressionMethods
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
class LiteralString
|
|
156
|
+
include Sequel::Postgres::PGRowOp::ExpressionMethods
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
if Sequel.core_extensions?
|
|
161
|
+
class Symbol
|
|
162
|
+
include Sequel::Postgres::PGRowOp::ExpressionMethods
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -20,13 +20,13 @@ module Sequel
|
|
|
20
20
|
#
|
|
21
21
|
# dataset = DB[:items].query do
|
|
22
22
|
# select :x, :y, :z
|
|
23
|
-
# filter{
|
|
24
|
-
#
|
|
23
|
+
# filter{(x > 1) & (y > 2)}
|
|
24
|
+
# reverse :z
|
|
25
25
|
# end
|
|
26
26
|
#
|
|
27
27
|
# Which is the same as:
|
|
28
28
|
#
|
|
29
|
-
# dataset = DB[:items].select(:x, :y, :z).filter{
|
|
29
|
+
# dataset = DB[:items].select(:x, :y, :z).filter{(x > 1) & (y > 2)}.reverse(:z)
|
|
30
30
|
#
|
|
31
31
|
# Note that inside a call to query, you cannot call each, insert, update,
|
|
32
32
|
# or delete (or any method that calls those), or Sequel will raise an
|
|
@@ -97,14 +97,13 @@ END_MIG
|
|
|
97
97
|
|
|
98
98
|
private
|
|
99
99
|
|
|
100
|
-
# If a database default exists and can't be converted,
|
|
101
|
-
# method modified
|
|
102
|
-
# :same_db option is used.
|
|
100
|
+
# If a database default exists and can't be converted, and we are dumping with :same_db,
|
|
101
|
+
# return a string with the inspect method modified a literal string is created if the code is evaled.
|
|
103
102
|
def column_schema_to_ruby_default_fallback(default, options)
|
|
104
103
|
if default.is_a?(String) && options[:same_db] && use_column_schema_to_ruby_default_fallback?
|
|
105
|
-
default = default.
|
|
104
|
+
default = default.dup
|
|
106
105
|
def default.inspect
|
|
107
|
-
"#{super}
|
|
106
|
+
"Sequel::LiteralString.new(#{super})"
|
|
108
107
|
end
|
|
109
108
|
default
|
|
110
109
|
end
|
|
@@ -137,7 +136,7 @@ END_MIG
|
|
|
137
136
|
gen.foreign_key(name, table, col_opts)
|
|
138
137
|
else
|
|
139
138
|
gen.column(name, type, col_opts)
|
|
140
|
-
if
|
|
139
|
+
if [Integer, Bignum, Float].include?(type) && schema[:db_type] =~ / unsigned\z/io
|
|
141
140
|
Sequel.extension :eval_inspect
|
|
142
141
|
gen.check(Sequel::SQL::Identifier.new(name) >= 0)
|
|
143
142
|
end
|
|
@@ -162,7 +161,7 @@ END_MIG
|
|
|
162
161
|
{:type =>schema[:type] == :boolean ? TrueClass : Integer}
|
|
163
162
|
when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/o
|
|
164
163
|
{:type=>Bignum}
|
|
165
|
-
when /\A(?:real|float|double(?: precision)?)\z/o
|
|
164
|
+
when /\A(?:real|float|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/o
|
|
166
165
|
{:type=>Float}
|
|
167
166
|
when 'boolean'
|
|
168
167
|
{:type=>TrueClass}
|
|
@@ -371,7 +370,7 @@ END_MIG
|
|
|
371
370
|
[sorted_tables, skipped_foreign_keys]
|
|
372
371
|
end
|
|
373
372
|
|
|
374
|
-
# Don't use
|
|
373
|
+
# Don't use a literal string fallback on MySQL, since the defaults it uses aren't
|
|
375
374
|
# valid literal SQL values.
|
|
376
375
|
def use_column_schema_to_ruby_default_fallback?
|
|
377
376
|
database_type != :mysql
|
|
@@ -24,7 +24,7 @@ module Sequel
|
|
|
24
24
|
# In this case, the code will currently use unqualified column names for all columns
|
|
25
25
|
# the dataset returns, except for the columns given.
|
|
26
26
|
# * This dataset has an existing explicit selection containing an item that returns
|
|
27
|
-
# multiple database columns (e.g. :table.*, 'column1, column2'
|
|
27
|
+
# multiple database columns (e.g. Sequel.expr(:table).*, Sequel.lit('column1, column2')). In this case,
|
|
28
28
|
# the behavior is undefined and this method should not be used.
|
|
29
29
|
#
|
|
30
30
|
# There may be other cases where this method does not work correctly, use it with caution.
|
data/lib/sequel/model/base.rb
CHANGED
|
@@ -872,6 +872,7 @@ module Sequel
|
|
|
872
872
|
# Artist.new(:name=>'Bob').values # => {:name=>'Bob'}
|
|
873
873
|
# Artist[1].values # => {:id=>1, :name=>'Jim', ...}
|
|
874
874
|
attr_reader :values
|
|
875
|
+
alias to_hash values
|
|
875
876
|
|
|
876
877
|
# Creates new instance and passes the given values to set.
|
|
877
878
|
# If a block is given, yield the instance to the block unless
|
data/lib/sequel/no_core_ext.rb
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
::SEQUEL_NO_CORE_EXTENSIONS = true
|
|
1
|
+
::SEQUEL_NO_CORE_EXTENSIONS = true unless defined?(::SEQUEL_NO_CORE_EXTENSIONS)
|
|
2
2
|
require 'sequel'
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The pg_row plugin allows you to use Sequel::Model classes as composite type
|
|
4
|
+
# classes, via the pg_row extension. So if you have an address table:
|
|
5
|
+
#
|
|
6
|
+
# DB.create_table(:address) do
|
|
7
|
+
# String :street
|
|
8
|
+
# String :city
|
|
9
|
+
# String :zip
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# and a company table with an address:
|
|
13
|
+
#
|
|
14
|
+
# DB.create_table(:company) do
|
|
15
|
+
# String :name
|
|
16
|
+
# address :address
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# You can create a Sequel::Model for the address table, and load the plugin,
|
|
20
|
+
# which registers the row type:
|
|
21
|
+
#
|
|
22
|
+
# class Address < Sequel::Model(:address)
|
|
23
|
+
# plugin :pg_row
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# Then when you select from the company table (even using a plain dataset),
|
|
27
|
+
# it will return address values as instances of Address:
|
|
28
|
+
#
|
|
29
|
+
# DB[:company].first
|
|
30
|
+
# # => {:name=>'MS', :address=>
|
|
31
|
+
# Address.load(:street=>'123 Foo St', :city=>'Bar Town', :zip=>'12345')}
|
|
32
|
+
#
|
|
33
|
+
# If you want a lot of your models to be used as row types, you can load the
|
|
34
|
+
# plugin into Sequel::Model itself:
|
|
35
|
+
#
|
|
36
|
+
# Sequel::Model.plugin :pg_row
|
|
37
|
+
#
|
|
38
|
+
# And then call register_row_type in the class
|
|
39
|
+
#
|
|
40
|
+
# Address.register_row_type
|
|
41
|
+
#
|
|
42
|
+
# Note that automatic conversion only works with the native postgres adapter.
|
|
43
|
+
# For other adapters that connect to PostgreSQL, you need to call the conversion
|
|
44
|
+
# proc manually.
|
|
45
|
+
#
|
|
46
|
+
# In addition to returning row-valued/composite types as instances of Sequel::Model,
|
|
47
|
+
# this also lets you use model instances in datasets when inserting, updating, and
|
|
48
|
+
# filtering:
|
|
49
|
+
#
|
|
50
|
+
# DB[:company].insert(:name=>'MS', :address=>
|
|
51
|
+
# Address.load(:street=>'123 Foo St', :city=>'Bar Town', :zip=>'12345'))
|
|
52
|
+
module PgRow
|
|
53
|
+
# When loading the extension, make sure the database has the pg_row extension
|
|
54
|
+
# loaded, load the custom database extensions, and automatically register the
|
|
55
|
+
# row type if the model has a dataset.
|
|
56
|
+
def self.configure(model)
|
|
57
|
+
model.db.extension(:pg_row)
|
|
58
|
+
model.db.extend(DatabaseMethods)
|
|
59
|
+
model.register_row_type if model.instance_variable_get(:@dataset)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module DatabaseMethods
|
|
63
|
+
ESCAPE_RE = /("|\\)/.freeze
|
|
64
|
+
ESCAPE_REPLACEMENT = '\\\\\1'.freeze
|
|
65
|
+
COMMA = ','
|
|
66
|
+
|
|
67
|
+
# Handle Sequel::Model instances in bound variables.
|
|
68
|
+
def bound_variable_arg(arg, conn)
|
|
69
|
+
case arg
|
|
70
|
+
when Sequel::Model
|
|
71
|
+
"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA)})"
|
|
72
|
+
else
|
|
73
|
+
super
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# If a Sequel::Model instance is given, return it as-is
|
|
78
|
+
# instead of attempting to convert it.
|
|
79
|
+
def row_type(db_type, v)
|
|
80
|
+
if v.is_a?(Sequel::Model)
|
|
81
|
+
v
|
|
82
|
+
else
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# Handle Sequel::Model instances in bound variable arrays.
|
|
90
|
+
def bound_variable_array(arg)
|
|
91
|
+
case arg
|
|
92
|
+
when Sequel::Model
|
|
93
|
+
"\"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
|
|
94
|
+
else
|
|
95
|
+
super
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
module ClassMethods
|
|
101
|
+
# Register the model's row type with the database.
|
|
102
|
+
def register_row_type
|
|
103
|
+
db.register_row_type(model.table_name, :converter=>self, :typecaster=>method(:new))
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
module InstanceMethods
|
|
108
|
+
ROW = 'ROW'.freeze
|
|
109
|
+
CAST = '::'.freeze
|
|
110
|
+
|
|
111
|
+
# Literalize the model instance and append it to the sql.
|
|
112
|
+
def sql_literal_append(ds, sql)
|
|
113
|
+
sql << ROW
|
|
114
|
+
ds.literal_append(sql, values.values_at(*columns))
|
|
115
|
+
sql << CAST
|
|
116
|
+
ds.quote_schema_table_append(sql, model.table_name)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The PgTypecastOnLoad plugin exists because when you connect to PostgreSQL
|
|
4
|
+
# using the do, swift, or jdbc adapter, Sequel doesn't have complete
|
|
5
|
+
# control over typecasting, and may return columns as strings instead of how
|
|
6
|
+
# the native postgres adapter would typecast them. This is mostly needed for
|
|
7
|
+
# the additional support that the pg_* extensions add for advanced PostgreSQL
|
|
8
|
+
# types such as arrays.
|
|
9
|
+
#
|
|
10
|
+
# This plugin modifies Model#set_values to do the same conversion that the
|
|
11
|
+
# native postgres adapter would do for all columns given. You can either
|
|
12
|
+
# specify the columns to typecast on load in the plugin call itself, or
|
|
13
|
+
# afterwards using add_pg_typecast_on_load_columns:
|
|
14
|
+
#
|
|
15
|
+
# # aliases => text[] column
|
|
16
|
+
# # config => hstore column
|
|
17
|
+
#
|
|
18
|
+
# Album.plugin :pg_typecast_on_load, :aliases, :config
|
|
19
|
+
# # or:
|
|
20
|
+
# Album.plugin :pg_typecast_on_load
|
|
21
|
+
# Album.add_pg_typecast_on_load_columns :aliases, :config
|
|
22
|
+
#
|
|
23
|
+
# This plugin only handles values that the adapter returns as strings. If
|
|
24
|
+
# the adapter returns a value other than a string for a column, that value
|
|
25
|
+
# will be used directly without typecasting.
|
|
26
|
+
module PgTypecastOnLoad
|
|
27
|
+
# Call add_pg_typecast_on_load_columns on the passed column arguments.
|
|
28
|
+
def self.configure(model, *columns)
|
|
29
|
+
model.instance_eval do
|
|
30
|
+
@pg_typecast_on_load_columns ||= []
|
|
31
|
+
add_pg_typecast_on_load_columns(*columns)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module ClassMethods
|
|
36
|
+
# The columns to typecast on load for this model.
|
|
37
|
+
attr_reader :pg_typecast_on_load_columns
|
|
38
|
+
|
|
39
|
+
# Add additional columns to typecast on load for this model.
|
|
40
|
+
def add_pg_typecast_on_load_columns(*columns)
|
|
41
|
+
@pg_typecast_on_load_columns.concat(columns)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Give the subclass a copy of the columns to typecast on load.
|
|
45
|
+
def inherited(subclass)
|
|
46
|
+
super
|
|
47
|
+
subclass.instance_variable_set(:@pg_typecast_on_load_columns, pg_typecast_on_load_columns.dup)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module InstanceMethods
|
|
52
|
+
# Lookup the conversion proc for the column's oid in the Database
|
|
53
|
+
# object, and use it to convert the value.
|
|
54
|
+
def set_values(values)
|
|
55
|
+
model.pg_typecast_on_load_columns.each do |c|
|
|
56
|
+
if (v = values[c]).is_a?(String) && (oid = db_schema[c][:oid])
|
|
57
|
+
values[c] = db.conversion_procs[oid].call(v)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -40,6 +40,37 @@ module Sequel
|
|
|
40
40
|
# changing the values of the Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS hash. You
|
|
41
41
|
# change change the default options on a per model basis
|
|
42
42
|
# by overriding a private instance method default_validation_helpers_options.
|
|
43
|
+
#
|
|
44
|
+
# By changing the default options, you can setup internationalization of the
|
|
45
|
+
# error messages. For example, you would modify the default options:
|
|
46
|
+
#
|
|
47
|
+
# Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS.merge!(
|
|
48
|
+
# :exact_length=>{:message=>lambda{|exact| I18n.t("errors.exact_length", :exact => exact)}},
|
|
49
|
+
# :integer=>{:message=>lambda{I18n.t("errors.integer")}},
|
|
50
|
+
# ...
|
|
51
|
+
# )
|
|
52
|
+
#
|
|
53
|
+
# and then use something like this in your yaml translation file:
|
|
54
|
+
#
|
|
55
|
+
# en:
|
|
56
|
+
# errors:
|
|
57
|
+
# exact_length: "is not %{exact} characters"
|
|
58
|
+
# integer: "is not a number"
|
|
59
|
+
#
|
|
60
|
+
# Note that if you want to support internationalization of Errors#full_messages,
|
|
61
|
+
# you need to override the method. Here's an example:
|
|
62
|
+
#
|
|
63
|
+
# class Sequel::Model::Errors
|
|
64
|
+
# ATTRIBUTE_JOINER = I18n.t('errors.joiner').freeze
|
|
65
|
+
# def full_messages
|
|
66
|
+
# inject([]) do |m, kv|
|
|
67
|
+
# att, errors = *kv
|
|
68
|
+
# att.is_a?(Array) ? Array(att).map!{|v| I18n.t("attributes.#{v}")} : att = I18n.t("attributes.#{att}")
|
|
69
|
+
# errors.each {|e| m << (e.is_a?(LiteralString) ? e : "#{Array(att).join(ATTRIBUTE_JOINER)} #{e}")}
|
|
70
|
+
# m
|
|
71
|
+
# end
|
|
72
|
+
# end
|
|
73
|
+
# end
|
|
43
74
|
module ValidationHelpers
|
|
44
75
|
# Default validation options used by Sequel. Can be modified to change the error
|
|
45
76
|
# messages for all models (e.g. for internationalization), or to set certain
|