flounder 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/HISTORY +5 -0
- data/flounder.gemspec +1 -1
- data/lib/flounder/query/select.rb +13 -2
- data/qed/flounder.sql +3 -2
- data/qed/index.md +0 -50
- data/qed/joins.md +65 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea885fee238d20c64364425cad9315b36a857c5a
|
4
|
+
data.tar.gz: 213baa1bff13a230263f742537667474e54fecee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05ecfc437a608a3e87369501f7ab3ffba0373e731752bdaa1fd4d89d7736e6479c33eea3e3ae589e6ae9ba7fb12f699e9100b3d88c763efecbda93e0c1368ae1
|
7
|
+
data.tar.gz: 82059189fb7904376ca5d03bd9ceca30fe77e47f67ab61280c49f66b4f9764ac3b34b1710c6f5791f9e66c4d2df64dd2b456309a12ceba636247880714de6409
|
data/HISTORY
CHANGED
data/flounder.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "flounder"
|
5
|
-
s.version = '0.
|
5
|
+
s.version = '0.11.0'
|
6
6
|
s.summary = "Flounder is a way to write SQL simply in Ruby. It deals with everything BUT object relational mapping. "
|
7
7
|
s.email = "kaspar.schiess@technologyastronauts.ch"
|
8
8
|
s.homepage = "https://bitbucket.org/technologyastronauts/oss_flounder"
|
@@ -14,6 +14,9 @@ module Flounder::Query
|
|
14
14
|
@projection_prefixes = Hash.new
|
15
15
|
@default_projection = []
|
16
16
|
|
17
|
+
# by default, joins are made to the entity that you start the query with
|
18
|
+
@join_entity = entity
|
19
|
+
|
17
20
|
add_fields_to_default from_entity
|
18
21
|
|
19
22
|
manager.from from_entity.table
|
@@ -30,6 +33,8 @@ module Flounder::Query
|
|
30
33
|
# prefix during this query.
|
31
34
|
attr_reader :projection_prefixes
|
32
35
|
|
36
|
+
attr_reader :join_entity, :last_join
|
37
|
+
|
33
38
|
def _join join_node, entity
|
34
39
|
entity = convert_to_entity(entity)
|
35
40
|
|
@@ -67,11 +72,17 @@ module Flounder::Query
|
|
67
72
|
|
68
73
|
def on join_conditions
|
69
74
|
manager.on(
|
70
|
-
*join_conditions.map { |(k, v)| transform_tuple(
|
75
|
+
*join_conditions.map { |(k, v)| transform_tuple(
|
76
|
+
join_entity, k, join_field(v)) })
|
71
77
|
self
|
72
78
|
end
|
79
|
+
|
73
80
|
def anchor
|
74
|
-
@
|
81
|
+
@join_entity = last_join
|
82
|
+
self
|
83
|
+
end
|
84
|
+
def hoist
|
85
|
+
@join_entity = entity
|
75
86
|
self
|
76
87
|
end
|
77
88
|
|
data/qed/flounder.sql
CHANGED
@@ -30,11 +30,12 @@ DROP TABLE IF EXISTS "comments" CASCADE;
|
|
30
30
|
CREATE TABLE "comments" (
|
31
31
|
"id" serial PRIMARY KEY,
|
32
32
|
"post_id" int NOT NULL REFERENCES posts("id"),
|
33
|
-
"text" text NOT NULL
|
33
|
+
"text" text NOT NULL,
|
34
|
+
"author_id" int not null REFERENCES users("id")
|
34
35
|
);
|
35
36
|
|
36
37
|
BEGIN;
|
37
|
-
INSERT INTO "comments" (post_id, text) VALUES (1, 'A silly comment.');
|
38
|
+
INSERT INTO "comments" (post_id, text, author_id) VALUES (1, 'A silly comment.', 1);
|
38
39
|
COMMIT;
|
39
40
|
|
40
41
|
-- import using
|
data/qed/index.md
CHANGED
@@ -72,56 +72,6 @@ By default, symbols are interpreted as field names in the entity that you start
|
|
72
72
|
assert generates_sql("SELECT [fields] FROM \"users\" WHERE \"posts\".\"id\" = 1")
|
73
73
|
~~~
|
74
74
|
|
75
|
-
# Some JOINs
|
76
|
-
|
77
|
-
Here are some non-crazy joins that also work.
|
78
|
-
|
79
|
-
~~~ruby
|
80
|
-
domain[:users].join(domain[:posts]).on(:id => :user_id).
|
81
|
-
assert generates_sql(%Q(SELECT [fields] FROM "users" INNER JOIN "posts" ON "users"."id" = "posts"."user_id"))
|
82
|
-
|
83
|
-
domain[:users].outer_join(domain[:posts]).on(:id => :user_id).
|
84
|
-
assert generates_sql(%Q(SELECT [fields] FROM "users" LEFT OUTER JOIN "posts" ON "users"."id" = "posts"."user_id"))
|
85
|
-
~~~
|
86
|
-
|
87
|
-
You might want to drop the calls to domain.
|
88
|
-
|
89
|
-
~~~ruby
|
90
|
-
domain[:users].join(:posts).on(:id => :user_id).
|
91
|
-
assert generates_sql(
|
92
|
-
%Q(SELECT [fields] FROM "users" INNER JOIN "posts" ON "users"."id" = "posts"."user_id"))
|
93
|
-
~~~
|
94
|
-
|
95
|
-
Joining presents an interesting dilemma. There are two ways of joining things together, given three tables. The sequence A.B.C might mean to join A to B and C; it might also be interpreted to mean to join A to B and B to C. Here's how we solve this.
|
96
|
-
|
97
|
-
~~~ruby
|
98
|
-
domain[:users].
|
99
|
-
join(domain[:posts]).on(:id => :user_id).
|
100
|
-
join(domain[:comments]).on(:id => :post_id).
|
101
|
-
assert generates_sql(%Q(SELECT [fields] FROM "users" INNER JOIN "posts" ON "users"."id" = "posts"."user_id" INNER JOIN "comments" ON "users"."id" = "comments"."post_id"))
|
102
|
-
~~~
|
103
|
-
|
104
|
-
So just doing `A.B.C` will give you the first of the above possibilities. Here's how to achive the second effect.
|
105
|
-
|
106
|
-
~~~ruby
|
107
|
-
domain[:users].
|
108
|
-
join(domain[:posts]).on(:id => :user_id).anchor.
|
109
|
-
join(domain[:comments]).on(:id => :post_id).
|
110
|
-
assert generates_sql(%Q(SELECT [fields] FROM "users" INNER JOIN "posts" ON "users"."id" = "posts"."user_id" INNER JOIN "comments" ON "posts"."id" = "comments"."post_id"))
|
111
|
-
~~~
|
112
|
-
|
113
|
-
The call to `#anchor` anchors all further joins at that point.
|
114
|
-
|
115
|
-
# Ordering records
|
116
|
-
|
117
|
-
~~~ruby
|
118
|
-
domain[:users].where(id: 2013).order_by(domain[:users][:id]).
|
119
|
-
assert generates_sql(%Q(SELECT [fields] FROM "users" WHERE "users"."id" = 2013 ORDER BY "users"."id"))
|
120
|
-
|
121
|
-
domain[:users].order_by(:id).
|
122
|
-
assert generates_sql(%Q(SELECT [fields] FROM "users" ORDER BY "users"."id"))
|
123
|
-
~~~
|
124
|
-
|
125
75
|
# Selective projection
|
126
76
|
|
127
77
|
~~~ruby
|
data/qed/joins.md
CHANGED
@@ -11,7 +11,7 @@ Joins are correctly created.
|
|
11
11
|
row.post.id.assert == 1
|
12
12
|
~~~
|
13
13
|
|
14
|
-
|
14
|
+
Join conditions are joined using `AND`.
|
15
15
|
|
16
16
|
~~~ruby
|
17
17
|
query = domain[:posts].
|
@@ -19,3 +19,67 @@ Joins are … joined using AND.
|
|
19
19
|
|
20
20
|
query.assert generates_sql(%Q(SELECT [fields] FROM "posts" INNER JOIN "users" ON "posts"."user_id" = "users"."id" AND "posts"."strange_example_id" = "users"."id"))
|
21
21
|
~~~
|
22
|
+
|
23
|
+
You might want to drop the calls to domain.
|
24
|
+
|
25
|
+
~~~ruby
|
26
|
+
domain[:users].join(:posts).on(:id => :user_id).
|
27
|
+
assert generates_sql(
|
28
|
+
%Q(SELECT [fields] FROM "users" INNER JOIN "posts" ON "users"."id" = "posts"."user_id"))
|
29
|
+
~~~
|
30
|
+
|
31
|
+
|
32
|
+
# Anchoring
|
33
|
+
|
34
|
+
Joining presents an interesting dilemma. There are two ways of joining things together, given three tables. The sequence A.B.C might mean to join A to B and C; it might also be interpreted to mean to join A to B and B to C. Here's how we solve this.
|
35
|
+
|
36
|
+
~~~ruby
|
37
|
+
domain[:users].
|
38
|
+
join(domain[:posts]).on(:id => :user_id).
|
39
|
+
join(domain[:comments]).on(:id => :post_id).
|
40
|
+
assert generates_sql(%Q(SELECT [fields] FROM "users" INNER JOIN "posts" ON "users"."id" = "posts"."user_id" INNER JOIN "comments" ON "users"."id" = "comments"."post_id"))
|
41
|
+
~~~
|
42
|
+
|
43
|
+
So just doing `A.B.C` will give you the first of the above possibilities. Here's how to achive the second effect.
|
44
|
+
|
45
|
+
~~~ruby
|
46
|
+
users.
|
47
|
+
join(:posts).on(:id => :user_id).anchor.
|
48
|
+
join(:comments).on(:id => :post_id).
|
49
|
+
assert generates_sql(%Q(SELECT [fields] FROM "users" INNER JOIN "posts" ON "users"."id" = "posts"."user_id" INNER JOIN "comments" ON "posts"."id" = "comments"."post_id"))
|
50
|
+
~~~
|
51
|
+
|
52
|
+
During the construction of the query, flounder keeps track of what table joins are relative to. That table also provides field resolution for the left hand side of the `#on` clause:
|
53
|
+
|
54
|
+
A.join(B).on(:a_field => :b_field).
|
55
|
+
join(C).on(:a_field => :c_field)
|
56
|
+
|
57
|
+
By default, A in the above example will always be the entity that you started the query with. To anchor a join on a different entity, you will need to join that entity in and then end the join with a call to `#anchor`:
|
58
|
+
|
59
|
+
A.join(B).on(:a_field => :b_field).anchor.
|
60
|
+
join(C).on(:b_field => :c_field)
|
61
|
+
|
62
|
+
To get rid of the anchor, you will need to call `#hoist`. This will always reset the left side of all further joins to the entity that you started the
|
63
|
+
query with:
|
64
|
+
|
65
|
+
A.join(B).on(:a_field => :b_field).anchor.
|
66
|
+
join(C).on(:b_field => :c_field).hoist.
|
67
|
+
join(D).on(:a_field => :d_field)
|
68
|
+
|
69
|
+
Here's a real usage example.
|
70
|
+
|
71
|
+
~~~ruby
|
72
|
+
query = users.
|
73
|
+
join(:posts).on(:id => :user_id).anchor.
|
74
|
+
join(:comments).on(:id => :post_id).hoist.
|
75
|
+
join(comments.as(:other_comments, :other_comment)).on(:id => :author_id)
|
76
|
+
|
77
|
+
query.assert generates_sql("SELECT [fields] FROM \"users\" INNER JOIN \"posts\" ON \"users\".\"id\" = \"posts\".\"user_id\" INNER JOIN \"comments\" ON \"posts\".\"id\" = \"comments\".\"post_id\" INNER JOIN \"comments\" \"other_comments\" ON \"users\".\"id\" = \"other_comments\".\"author_id\"")
|
78
|
+
~~~
|
79
|
+
|
80
|
+
Results should also be correct:
|
81
|
+
|
82
|
+
~~~ruby
|
83
|
+
row = query.first
|
84
|
+
row.other_comment.assert == row.comment
|
85
|
+
~~~
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flounder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kaspar Schiess
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-08-
|
12
|
+
date: 2014-08-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: arel
|