flounder 0.8.1 → 0.9.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.
- checksums.yaml +4 -4
- data/HISTORY +7 -0
- data/flounder-0.3.0.gem +0 -0
- data/flounder.gemspec +2 -2
- data/lib/flounder/connection.rb +38 -10
- data/lib/flounder/connection_pool.rb +11 -2
- data/lib/flounder/domain.rb +19 -0
- data/lib/flounder/engine.rb +8 -0
- data/lib/flounder/entity.rb +55 -53
- data/lib/flounder/exceptions.rb +8 -0
- data/lib/flounder/field.rb +10 -2
- data/lib/flounder/query/base.rb +157 -0
- data/lib/flounder/{immediate.rb → query/immediate.rb} +1 -1
- data/lib/flounder/query/insert.rb +41 -0
- data/lib/flounder/query/returning.rb +19 -0
- data/lib/flounder/{query.rb → query/select.rb} +23 -106
- data/lib/flounder/query/update.rb +45 -0
- data/lib/flounder/symbol_extensions.rb +3 -0
- data/lib/flounder.rb +6 -4
- data/qed/applique/ae.rb +1 -1
- data/qed/atomic_insert_update.md +33 -0
- data/qed/conditions.md +12 -6
- data/qed/exceptions.md +22 -1
- data/qed/index.md +37 -3
- data/qed/inserts.md +5 -5
- data/qed/ordering.md +21 -14
- data/qed/projection.md +1 -0
- data/qed/updates.md +15 -10
- metadata +14 -11
- data/Gemfile.lock +0 -33
- data/lib/flounder/insert.rb +0 -73
- data/lib/flounder/update.rb +0 -114
- data/qed/insdate.md +0 -25
@@ -1,14 +1,14 @@
|
|
1
|
-
|
1
|
+
|
2
|
+
require_relative 'base'
|
3
|
+
|
4
|
+
module Flounder::Query
|
2
5
|
|
3
6
|
# A query obtained by calling any of the chain methods on an entity.
|
4
7
|
#
|
5
|
-
class
|
8
|
+
class Select < Base
|
6
9
|
def initialize domain, from_entity
|
7
|
-
|
8
|
-
|
9
|
-
@engine = Engine.new(from_entity.domain.connection_pool)
|
10
|
-
@manager = Arel::SelectManager.new(@engine)
|
11
|
-
|
10
|
+
super domain, Arel::SelectManager, from_entity
|
11
|
+
|
12
12
|
@has_projection = false
|
13
13
|
|
14
14
|
@projection_prefixes = Hash.new
|
@@ -19,15 +19,8 @@ module Flounder
|
|
19
19
|
manager.from from_entity.table
|
20
20
|
end
|
21
21
|
|
22
|
-
# Domain that this query was issued from.
|
23
|
-
attr_reader :domain
|
24
|
-
# Entity that this query acts on.
|
25
|
-
attr_reader :from_entity
|
26
|
-
|
27
22
|
# Arel SqlManager that accumulates this query.
|
28
23
|
attr_reader :manager
|
29
|
-
# Database engine that links Arel to Postgres.
|
30
|
-
attr_reader :engine
|
31
24
|
|
32
25
|
# All projected fields if no custom projection is made. Fields are encoded
|
33
26
|
# so that they can be traced back to the entity that contributed them.
|
@@ -37,13 +30,6 @@ module Flounder
|
|
37
30
|
# prefix during this query.
|
38
31
|
attr_reader :projection_prefixes
|
39
32
|
|
40
|
-
def where conditions
|
41
|
-
conditions.each do |k, v|
|
42
|
-
manager.where(transform_hash(k, v))
|
43
|
-
end
|
44
|
-
self
|
45
|
-
end
|
46
|
-
|
47
33
|
def _join join_node, entity
|
48
34
|
@last_join = entity
|
49
35
|
|
@@ -53,6 +39,7 @@ module Flounder
|
|
53
39
|
|
54
40
|
self
|
55
41
|
end
|
42
|
+
|
56
43
|
def add_fields_to_default entity
|
57
44
|
prefix = entity.name.to_s
|
58
45
|
table = entity.table
|
@@ -79,12 +66,12 @@ module Flounder
|
|
79
66
|
def on join_conditions
|
80
67
|
join_conditions.each do |k, v|
|
81
68
|
manager.on(
|
82
|
-
|
69
|
+
transform_tuple(k, join_field(v)))
|
83
70
|
end
|
84
71
|
self
|
85
72
|
end
|
86
73
|
def anchor
|
87
|
-
@
|
74
|
+
@entity = @last_join
|
88
75
|
self
|
89
76
|
end
|
90
77
|
|
@@ -115,14 +102,7 @@ module Flounder
|
|
115
102
|
self
|
116
103
|
end
|
117
104
|
|
118
|
-
|
119
|
-
def to_sql
|
120
|
-
prepare_kick
|
121
|
-
|
122
|
-
manager.to_sql.tap { |sql|
|
123
|
-
domain.log_sql(sql) }
|
124
|
-
end
|
125
|
-
alias sql to_sql
|
105
|
+
alias all kick
|
126
106
|
|
127
107
|
def each &block
|
128
108
|
all.each(&block)
|
@@ -140,46 +120,20 @@ module Flounder
|
|
140
120
|
|
141
121
|
all.first.count
|
142
122
|
end
|
143
|
-
def delete
|
144
|
-
# things.where(...).delete.all
|
145
|
-
#
|
146
|
-
# Code that I actually want:
|
147
|
-
#
|
148
|
-
# @manager = manager.compile_delete
|
149
|
-
# self
|
150
|
-
#
|
151
|
-
# But for now it's a kicker:
|
152
|
-
#
|
153
|
-
engine.exec(manager.compile_delete.to_sql)
|
154
|
-
end
|
155
123
|
|
156
|
-
|
157
|
-
# mapped to objects using the row mapper.
|
158
|
-
#
|
159
|
-
def all
|
160
|
-
all = nil
|
161
|
-
engine.exec(sql) do |result|
|
162
|
-
all = Array.new(result.ntuples, nil)
|
163
|
-
result.ntuples.times do |row_idx|
|
164
|
-
all[row_idx] = engine.connection.
|
165
|
-
objectify_result_row(from_entity, result, row_idx) do |name|
|
166
|
-
unless default_projection.empty?
|
167
|
-
extract_source_info_from_name(name)
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
124
|
+
private
|
172
125
|
|
173
|
-
|
126
|
+
def column_name_to_entity name
|
127
|
+
unless default_projection.empty?
|
128
|
+
extract_source_info_from_name(name)
|
129
|
+
end
|
174
130
|
end
|
175
|
-
|
176
|
-
private
|
177
131
|
|
178
132
|
# Transforms a simple symbol into either a field of the last .join table,
|
179
133
|
# or respects field values passed in.
|
180
134
|
#
|
181
135
|
def join_field name
|
182
|
-
return name if name.kind_of? Field
|
136
|
+
return name if name.kind_of? Flounder::Field
|
183
137
|
@last_join[name]
|
184
138
|
end
|
185
139
|
|
@@ -201,13 +155,14 @@ module Flounder
|
|
201
155
|
def map_to_field field_ref
|
202
156
|
case field_ref
|
203
157
|
when Symbol
|
204
|
-
|
158
|
+
entity[field_ref]
|
205
159
|
when String
|
206
160
|
Immediate.new(field_ref)
|
207
|
-
when Field
|
161
|
+
when Flounder::Field
|
208
162
|
field_ref
|
209
163
|
else
|
210
|
-
fail InvalidFieldReference,
|
164
|
+
fail Flounder::InvalidFieldReference,
|
165
|
+
"Cannot resolve #{field_ref.inspect} to a field."
|
211
166
|
end
|
212
167
|
end
|
213
168
|
|
@@ -251,47 +206,9 @@ module Flounder
|
|
251
206
|
when Flounder::Field
|
252
207
|
field.fully_qualified_name
|
253
208
|
when Flounder::SymbolExtensions::Modifier
|
254
|
-
field.to_arel_field(
|
255
|
-
else
|
256
|
-
from_entity[field].arel_field
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
# Called on each key/value pair of a
|
261
|
-
# * condition
|
262
|
-
# * join
|
263
|
-
# clause, this returns a field that can be passed to Arel
|
264
|
-
# * #where
|
265
|
-
# * #on
|
266
|
-
#
|
267
|
-
def transform_hash field, value
|
268
|
-
if value.kind_of? Field
|
269
|
-
value = value.arel_field
|
270
|
-
end
|
271
|
-
|
272
|
-
case field
|
273
|
-
when Symbol
|
274
|
-
join_and_condition_part(from_entity[field].arel_field, value)
|
275
|
-
when Flounder::Field
|
276
|
-
join_and_condition_part(field.arel_field, value)
|
277
|
-
when Flounder::SymbolExtensions::Modifier
|
278
|
-
join_and_condition_part(
|
279
|
-
field.to_arel_field(from_entity),
|
280
|
-
value,
|
281
|
-
field.kind)
|
282
|
-
else
|
283
|
-
fail "Could not transform condition part. (#{field.inspect}, #{value.inspect})"
|
284
|
-
end
|
285
|
-
end
|
286
|
-
def join_and_condition_part arel_field, value, kind=:eq
|
287
|
-
case value
|
288
|
-
when Symbol
|
289
|
-
value_field = from_entity[value].arel_field
|
290
|
-
arel_field.send(kind, value_field)
|
291
|
-
when Range
|
292
|
-
arel_field.in(value)
|
209
|
+
field.to_arel_field(entity).send field.kind
|
293
210
|
else
|
294
|
-
arel_field
|
211
|
+
entity[field].arel_field
|
295
212
|
end
|
296
213
|
end
|
297
214
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require_relative 'returning'
|
3
|
+
|
4
|
+
module Flounder::Query
|
5
|
+
|
6
|
+
# An update obtained by calling any of the chain methods on an entity.
|
7
|
+
#
|
8
|
+
class Update < Base
|
9
|
+
def initialize domain, entity
|
10
|
+
super(domain, Arel::UpdateManager, entity)
|
11
|
+
|
12
|
+
manager.table entity.table
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add one row to the updates.
|
16
|
+
#
|
17
|
+
def set fields
|
18
|
+
manager.set(
|
19
|
+
fields.map { |k, v|
|
20
|
+
transform_attributes(k, v) })
|
21
|
+
end
|
22
|
+
|
23
|
+
include Returning
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Called on each key/value pair of an update clause, this returns a
|
28
|
+
# hash that can be passed to Arel #update.
|
29
|
+
#
|
30
|
+
def transform_attributes field, value
|
31
|
+
if value.kind_of? Flounder::Field
|
32
|
+
value = value.arel_field
|
33
|
+
end
|
34
|
+
|
35
|
+
case field
|
36
|
+
when Symbol, String
|
37
|
+
[entity[field.to_sym].arel_field, value]
|
38
|
+
when Flounder::Field
|
39
|
+
[field.arel_field, value]
|
40
|
+
else
|
41
|
+
fail "Could not transform condition part. (#{field.inspect}, #{value.inspect})"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end # class
|
45
|
+
end # module Flounder
|
@@ -13,6 +13,9 @@ module Flounder
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
# NOTE mixing comparison ops with asc and desc is nasty, but not really
|
17
|
+
# relevant. Errors will get raised later on - we're not in the business of
|
18
|
+
# checking your SQL for validity.
|
16
19
|
[:not_eq, :lt, :gt, :gteq, :lteq, :matches, :asc, :desc].each do |kind|
|
17
20
|
define_method kind do
|
18
21
|
Modifier.new(self, kind)
|
data/lib/flounder.rb
CHANGED
@@ -12,10 +12,12 @@ require 'flounder/engine'
|
|
12
12
|
require 'flounder/entity'
|
13
13
|
require 'flounder/entity_alias'
|
14
14
|
require 'flounder/field'
|
15
|
-
|
16
|
-
require 'flounder/query'
|
17
|
-
require 'flounder/
|
18
|
-
require 'flounder/
|
15
|
+
|
16
|
+
require 'flounder/query/immediate'
|
17
|
+
require 'flounder/query/select'
|
18
|
+
require 'flounder/query/insert'
|
19
|
+
|
20
|
+
require 'flounder/query/update'
|
19
21
|
require 'flounder/exceptions'
|
20
22
|
|
21
23
|
module Flounder
|
data/qed/applique/ae.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
THIS IS A TODO, NOT A FEATURE
|
3
|
+
|
4
|
+
# Atomic INSERT, then UPDATE
|
5
|
+
|
6
|
+
As an experimental feature, `Flounder` allows you to combine `INSERT` and `UPDATE` statements into one statement. This is executed as atomically as possible on the database. Two methods are responsible for this bit of magic, both composed of parts of the word 'INSERT' and 'UPDATE': `#insdate` and `#upsert`. We'll expose the difference between these two later on, for now, let's just jump right ahead and use them as intended.
|
7
|
+
|
8
|
+
For example, let's look at inserting a user only if it doesn't exist on the database yet.
|
9
|
+
|
10
|
+
~~~ruby
|
11
|
+
# pre = users.count
|
12
|
+
#
|
13
|
+
# users.insdate(:name, name: 'upsert')
|
14
|
+
# users.insdate(:name, name: 'upsert')
|
15
|
+
#
|
16
|
+
# post = users.count
|
17
|
+
# post.assert == pre+1
|
18
|
+
~~~
|
19
|
+
|
20
|
+
# Intricacies of design
|
21
|
+
|
22
|
+
Here's a decomposition of what the insdate is behind the scenes, into its parts: It serves as an illustration for turtles all the way down:
|
23
|
+
~~~ruby
|
24
|
+
update = users.update(name: 'FooBar').where(name: 'BarBaz')
|
25
|
+
|
26
|
+
users.insert(name: 'FooBar').
|
27
|
+
with(:upsert, update).
|
28
|
+
where(:id.not_in => 'upsert.id').
|
29
|
+
assert generates_sql(
|
30
|
+
%Q(WITH upsert AS ()))
|
31
|
+
~~~
|
32
|
+
|
33
|
+
Note that the above code does strictly nothing, since Arel (our SQL generator) currently does not support `WITH` for `INSERT`/`UPDATE`. For this reason, we decide not to implement upsert/insdate at this point.
|
data/qed/conditions.md
CHANGED
@@ -5,9 +5,15 @@ A simple use case.
|
|
5
5
|
s2013.user.id.assert == 1
|
6
6
|
~~~
|
7
7
|
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
# Complex Conditions
|
9
|
+
|
10
|
+
Complex conditions can be written in SQL directly. You can use the postgres positional bind syntax to bind values to your conditions safely.
|
11
|
+
|
12
|
+
~~~ruby
|
13
|
+
query = domain[:posts].where(["id = ($1) OR title = ($2)", 1, "Hello"])
|
14
|
+
query.assert generates_sql(
|
15
|
+
%Q(SELECT [fields] FROM "posts" WHERE id = ($1) OR title = ($2)))
|
16
|
+
|
17
|
+
post = query.first
|
18
|
+
post.id.assert == 1
|
19
|
+
~~~
|
data/qed/exceptions.md
CHANGED
@@ -9,4 +9,25 @@ All of these statements raise an `InvalidFieldReference` exception.
|
|
9
9
|
expect Flounder::InvalidFieldReference do
|
10
10
|
users.project(1, 2, 3)
|
11
11
|
end
|
12
|
-
~~~
|
12
|
+
~~~
|
13
|
+
|
14
|
+
# Double projection
|
15
|
+
|
16
|
+
You cannot have the same field name twice in a result.
|
17
|
+
|
18
|
+
~~~ruby
|
19
|
+
expect Flounder::DuplicateField do
|
20
|
+
users.project('id, id').all
|
21
|
+
end
|
22
|
+
~~~
|
23
|
+
|
24
|
+
# Bind Index out of Bounds
|
25
|
+
|
26
|
+
Indices of bind expressions in `#where` need to be relative to the argument list given.
|
27
|
+
|
28
|
+
~~~ruby
|
29
|
+
expect Flounder::BindIndexOutOfBounds do
|
30
|
+
users.where('id = $100', 1)
|
31
|
+
end
|
32
|
+
~~~
|
33
|
+
|
data/qed/index.md
CHANGED
@@ -55,7 +55,12 @@ Fields can be used fully qualified by going through the entity.
|
|
55
55
|
assert generates_sql(%Q(SELECT [fields] FROM "users" WHERE "users"."id" = 10))
|
56
56
|
|
57
57
|
domain[:users].where(domain[:users][:name].matches => 'a%').
|
58
|
-
assert generates_sql(%Q(SELECT [fields] FROM "users" WHERE "users"."name"
|
58
|
+
assert generates_sql(%Q(SELECT [fields] FROM "users" WHERE "users"."name" ILIKE 'a%'))
|
59
|
+
~~~
|
60
|
+
|
61
|
+
~~~ruby
|
62
|
+
domain[:users].where(:user_id => :approver_id).
|
63
|
+
assert generates_sql("SELECT [fields] FROM \"users\" WHERE \"users\".\"user_id\" = \"users\".\"approver_id\"")
|
59
64
|
~~~
|
60
65
|
|
61
66
|
# Some JOINs
|
@@ -90,7 +95,7 @@ So just doing `A.B.C` will give you the first of the above possibilities. Here's
|
|
90
95
|
|
91
96
|
The call to `#anchor` anchors all further joins at that point.
|
92
97
|
|
93
|
-
#
|
98
|
+
# Ordering records
|
94
99
|
|
95
100
|
~~~ruby
|
96
101
|
domain[:users].where(id: 2013).order_by(domain[:users][:id]).
|
@@ -104,5 +109,34 @@ The call to `#anchor` anchors all further joins at that point.
|
|
104
109
|
|
105
110
|
~~~ruby
|
106
111
|
domain[:users].where(id: 2013).project(domain[:users][:id]).
|
107
|
-
|
112
|
+
to_sql.assert == %Q(SELECT "users"."id" FROM "users" WHERE "users"."id" = 2013)
|
113
|
+
~~~
|
114
|
+
|
115
|
+
# Transactions
|
116
|
+
|
117
|
+
Transactions are supported on the domain and on entities. Since you need to make sure that your transaction uses only one connection, you will need to handle connections explicitly.
|
118
|
+
|
119
|
+
~~~ruby
|
120
|
+
expect RuntimeError do
|
121
|
+
domain.with_connection do |conn|
|
122
|
+
conn.transaction do
|
123
|
+
posts.update(title: 'A single title for everyone').kick(conn)
|
124
|
+
fail 'rollback'
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
posts.first.title.assert != 'A single title for everyone'
|
108
130
|
~~~
|
131
|
+
|
132
|
+
The same works on any entity from the domain. Please note that there is a shortcut for getting at a transaction.
|
133
|
+
|
134
|
+
~~~ruby
|
135
|
+
domain.transaction do |conn|
|
136
|
+
posts.all(conn)
|
137
|
+
end
|
138
|
+
|
139
|
+
posts.transaction do |conn|
|
140
|
+
posts.all(conn)
|
141
|
+
end
|
142
|
+
~~~
|
data/qed/inserts.md
CHANGED
@@ -3,13 +3,13 @@ An insert creates a state from which SQL can be extracted.
|
|
3
3
|
~~~ruby
|
4
4
|
sql = users.insert(:name => 'Mr. Insert SQL').to_sql
|
5
5
|
|
6
|
-
sql.assert == "INSERT INTO \"users\" (\"name\") VALUES ('Mr. Insert SQL')"
|
6
|
+
sql.assert == "INSERT INTO \"users\" (\"name\") VALUES ('Mr. Insert SQL') RETURNING *"
|
7
7
|
~~~
|
8
8
|
|
9
9
|
Using returning will return the inserted object.
|
10
10
|
|
11
11
|
~~~ruby
|
12
|
-
results = users.insert(:name => 'Mr. Returning Asterisk').
|
12
|
+
results = users.insert(:name => 'Mr. Returning Asterisk').kick
|
13
13
|
|
14
14
|
results.first.name.assert == 'Mr. Returning Asterisk'
|
15
15
|
~~~
|
@@ -17,7 +17,7 @@ Using returning will return the inserted object.
|
|
17
17
|
Returning all fields is the default, but you can provide that explicitly.
|
18
18
|
|
19
19
|
~~~ruby
|
20
|
-
results = users.insert(:name => 'Mr. Returning Asterisk').returning
|
20
|
+
results = users.insert(:name => 'Mr. Returning Asterisk').returning('*').kick
|
21
21
|
|
22
22
|
results.first.name.assert == 'Mr. Returning Asterisk'
|
23
23
|
~~~
|
@@ -25,7 +25,7 @@ Returning all fields is the default, but you can provide that explicitly.
|
|
25
25
|
Using returning with a field name will return those fields of the inserted object.
|
26
26
|
|
27
27
|
~~~ruby
|
28
|
-
results = users.insert(:name => 'Mr. Returning').returning(:id)
|
28
|
+
results = users.insert(:name => 'Mr. Returning').returning(:id).kick
|
29
29
|
|
30
30
|
id = results.first.id
|
31
31
|
|
@@ -35,7 +35,7 @@ Using returning with a field name will return those fields of the inserted objec
|
|
35
35
|
Flounder fields can be used as keys.
|
36
36
|
|
37
37
|
~~~ruby
|
38
|
-
results = users.insert(users[:name] => 'Mr. Flounder Field').
|
38
|
+
results = users.insert(users[:name] => 'Mr. Flounder Field').kick
|
39
39
|
|
40
40
|
results.first.name.assert == 'Mr. Flounder Field'
|
41
41
|
~~~
|
data/qed/ordering.md
CHANGED
@@ -17,9 +17,9 @@ They can be ordered ASC.
|
|
17
17
|
They can also be ordered DESC.
|
18
18
|
|
19
19
|
~~~ruby
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
domain[:posts].order_by(:title.desc).
|
21
|
+
assert generates_sql(
|
22
|
+
'SELECT [fields] FROM "posts" ORDER BY "posts"."title" DESC')
|
23
23
|
~~~
|
24
24
|
|
25
25
|
Multiple fields can be used.
|
@@ -43,15 +43,22 @@ Ordering can be defined in many ways.
|
|
43
43
|
~~~ruby
|
44
44
|
user = users.first
|
45
45
|
|
46
|
-
inserted = posts.insert(
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
46
|
+
inserted = posts.insert(
|
47
|
+
title: 'ABC', text: 'Alphabet', user_id: user.id).kick
|
48
|
+
|
49
|
+
def titles_by *args
|
50
|
+
domain[:posts].order_by(*args).map(&:title)
|
51
|
+
end
|
52
|
+
|
53
|
+
titles_by(:title).assert == ['ABC', 'First Light']
|
54
|
+
titles_by(:title.asc).assert == ['ABC', 'First Light']
|
55
|
+
titles_by(:title.desc).assert == ['First Light', 'ABC']
|
56
|
+
|
57
|
+
titles_by('title').assert == ['ABC', 'First Light']
|
58
|
+
titles_by('title ASC').assert == ['ABC', 'First Light']
|
59
|
+
titles_by('title DESC').assert == ['First Light', 'ABC']
|
60
|
+
|
61
|
+
titles_by(posts[:title]).assert == ['ABC', 'First Light']
|
62
|
+
titles_by(posts[:title].asc).assert == ['ABC', 'First Light']
|
63
|
+
titles_by(posts[:title].desc).assert == ['First Light', 'ABC']
|
57
64
|
~~~
|
data/qed/projection.md
CHANGED
data/qed/updates.md
CHANGED
@@ -5,7 +5,7 @@ An update creates a state from which SQL can be extracted.
|
|
5
5
|
|
6
6
|
sql = posts.update(:title => 'Update SQL').where(:id => post.id).to_sql
|
7
7
|
|
8
|
-
sql.assert == 'UPDATE "posts" SET "title" = \'Update SQL\' WHERE "posts"."id" = 1'
|
8
|
+
sql.assert == 'UPDATE "posts" SET "title" = \'Update SQL\' WHERE "posts"."id" = 1 RETURNING *'
|
9
9
|
~~~
|
10
10
|
|
11
11
|
Flounder fields are ok.
|
@@ -13,17 +13,20 @@ Flounder fields are ok.
|
|
13
13
|
~~~ruby
|
14
14
|
post = posts.first
|
15
15
|
|
16
|
-
sql = posts.
|
16
|
+
sql = posts.
|
17
|
+
update(posts[:title] => 'Update Flounder SQL').
|
18
|
+
where(:id => post.id).to_sql
|
17
19
|
|
18
|
-
sql.assert == 'UPDATE "posts" SET "title" = \'Update Flounder SQL\' WHERE "posts"."id" = 1'
|
20
|
+
sql.assert == 'UPDATE "posts" SET "title" = \'Update Flounder SQL\' WHERE "posts"."id" = 1 RETURNING *'
|
19
21
|
~~~
|
20
22
|
|
21
23
|
It can update a single field.
|
22
24
|
|
23
25
|
~~~ruby
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
post_id = posts.first.id
|
27
|
+
post = posts.
|
28
|
+
update(title: 'Update Field').
|
29
|
+
where(id: post_id).kick.first
|
27
30
|
|
28
31
|
post.title.assert == 'Update Field'
|
29
32
|
~~~
|
@@ -33,7 +36,9 @@ Flounder fields are ok here too.
|
|
33
36
|
~~~ruby
|
34
37
|
post = posts.first
|
35
38
|
|
36
|
-
post = posts.
|
39
|
+
post = posts.
|
40
|
+
update(posts[:title] => 'Update Flounder Field').
|
41
|
+
where(:id => post.id).kick.first
|
37
42
|
|
38
43
|
post.title.assert == 'Update Flounder Field'
|
39
44
|
~~~
|
@@ -45,7 +50,7 @@ An update can take multiple fields.
|
|
45
50
|
|
46
51
|
sql = posts.update(:title => 'Update SQL', :text => 'Update Multiple Fields Text').where(:id => post.id).to_sql
|
47
52
|
|
48
|
-
sql.assert == 'UPDATE "posts" SET "title" = \'Update SQL\', "text" = \'Update Multiple Fields Text\' WHERE "posts"."id" = 1'
|
53
|
+
sql.assert == 'UPDATE "posts" SET "title" = \'Update SQL\', "text" = \'Update Multiple Fields Text\' WHERE "posts"."id" = 1 RETURNING *'
|
49
54
|
~~~
|
50
55
|
|
51
56
|
Updating a single row is possible.
|
@@ -53,7 +58,7 @@ Updating a single row is possible.
|
|
53
58
|
~~~ruby
|
54
59
|
post = posts.first
|
55
60
|
|
56
|
-
post = posts.update(:title => 'Updated Title', :text => 'Update Single Row Possible').where(:id => post.id).
|
61
|
+
post = posts.update(:title => 'Updated Title', :text => 'Update Single Row Possible').where(:id => post.id).kick.first
|
57
62
|
|
58
63
|
post.title.assert == 'Updated Title'
|
59
64
|
post.text.assert == 'Update Single Row Possible'
|
@@ -62,7 +67,7 @@ Updating a single row is possible.
|
|
62
67
|
Updating multiple rows is possible.
|
63
68
|
|
64
69
|
~~~ruby
|
65
|
-
updated = users.update(:name => 'Update Multiple Rows').where(:name.not_eq => nil).
|
70
|
+
updated = users.update(:name => 'Update Multiple Rows').where(:name.not_eq => nil).kick
|
66
71
|
|
67
72
|
updated.map(&:name).assert == ['Update Multiple Rows']*6
|
68
73
|
~~~
|