activerecord_where_assoc 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 95ce5c0d5802b14a7e1427d949bd45fec4156841
4
- data.tar.gz: 928080180b366f561b01cb26f7cf044187ac1c2b
3
+ metadata.gz: e248bec95c932b0bd11743e33d38915ebc13c398
4
+ data.tar.gz: bea0b06ff2f9e3f61a92e4e2f0cab78daaf63b9b
5
5
  SHA512:
6
- metadata.gz: d7e8dccc5e31da6f30a2355994b8ec611de26b015c3786524408f2fc6afb688e31381e71a8908f5df8682637c473a0a55a1d7f6769772389592cb706c316c3e1
7
- data.tar.gz: a31a92215befe043a240074e0b358502e943cfb097b1409155193497b6f69ec02251dd9d53c7af3ecf723d7d398c543bff598ba15bd4216ae79ed89838192e30
6
+ metadata.gz: 9551db7bfb1f1219dd1c23fe9255e4ffd495a4bf6777c53619d7037b432e1b13ecafeabee6be8083d8556bca49cf6473f3fbd4ff601723a1380f953a9dee764b
7
+ data.tar.gz: 5b1d53119ce95d0ab40665330f63c58512818f4af16352e5eb6a7245853ed15c40c2391152aa08bb894703ef853b775c94d3302c5641ae4372d75dfbda2e91fa
@@ -0,0 +1,222 @@
1
+ There are multiple ways of achieving results similar to what this gems does using either only built-in ActiveRecord functionalities or other gems.
2
+
3
+ This is a list of some of those alternatives, explaining what issues they have or reasons to prefer this gem over them.
4
+
5
+
6
+ ## Too long; didn't read
7
+
8
+ **Use this gem, you will avoid problems and save time**
9
+
10
+ * No more having to choose, case by case, which way has the less problems.
11
+ Just use `#where_assoc_*` each time and avoid every problems.
12
+ * Need less raw SQL, which means less code, more clarity and less maintenance.
13
+ * Allows powerful scopes without traps.
14
+ * Handles recursive associations correctly.
15
+ * Handles has_one correctly.
16
+
17
+ ## Short version
18
+
19
+ Summary of the problems of the alternatives that `activerecord_where_assoc` solves. The following sections go in more details.
20
+
21
+ * every alternatives (except raw SQL):
22
+ * treat `has_one` like a `has_many`.
23
+ * can't handle recursive associations. (ex: parent/children)
24
+ * no simple way of checking for more complex counts. (such as less than 5)
25
+ * `joins` / `includes`:
26
+ * doing `not exists` with conditions requires a `LEFT JOIN` with the conditions as part of the `ON`, which requires raw SQL.
27
+ * checking for 2 sets of conditions on different records of the same association won't work. (so your scopes becomes incompatible)
28
+ * can't be used with Rails 5's `or` unless both sides do the same `joins` / `includes` / `eager_load`.
29
+ * `joins`:
30
+ * `has_many` may return duplicate rows per record.
31
+ * using `uniq` / `distinct` to solve duplicate rows is an unexpected side-effect when this is in a scope.
32
+ * `includes`:
33
+ * triggers eagerloading, which makes your `scope` have unexpected bad performances if it's not necessary.
34
+ * when using a condition, the eagerloaded records are also filtered, which is very bug-prone when in a scope.
35
+ * can't do `not exists` with conditions.
36
+ * raw SQL:
37
+ * verbose, less clear on the goal of the queries (you don't even name the association the query is about).
38
+ * need to repeat conditions from the association / default_scope.
39
+ * `where_exists` gem:
40
+ * can't use scopes of the association's model.
41
+ * can't go deeper than one level of association.
42
+
43
+ ## Common problems to most alternatives
44
+
45
+ These are problems that affect most alternatives. Details are written in this section and just referred to by a one liner when they apply to an alternative.
46
+
47
+ ### Treating has_one like has_many
48
+
49
+ Every alternative treats a has_one just like a has_many. So if any of the records (instead of only the first) matches your condition, you will get a match.
50
+
51
+ And example to clarify:
52
+
53
+ ```ruby
54
+ class Person < ActiveRecord::Base
55
+ has_many :addresses
56
+ has_one :current_address, -> { order("effective_date DESC") }, class_name: 'Address'
57
+ end
58
+
59
+ # This correctly matches only those whose current_address is in Montreal
60
+ Person.where_assoc_exists(:current_address, city: 'Montreal')
61
+
62
+ # Every alternatives (except raw SQL):
63
+ # Matches those that have had an address in Montreal, no matter when
64
+ Person.where_assoc_exists(:addresses, city: 'Montreal')
65
+ ```
66
+
67
+ The general version of this problem is the handling of `limit` and `offset` on associations and in default_scopes. where_assoc_exists handle those correctly and only checks the records that match the limit and the offset.
68
+
69
+ ### Raw SQL joins or sub-selects
70
+
71
+ Having to write the joins and conditions in raw SQL is more painful and more error prone than having a method do it for you. It hides the important details of what you are doing in a lot of verbosity.
72
+
73
+ If there are conditions set on either the association or a default_scope of the model, then you must rewrite those conditions in your manual joins and your manual sub-selects. Worst, if you add/change those conditions on the association / default_scope, then you must find every raw SQL that apply and do the same operation.
74
+
75
+ ```ruby
76
+ class Post < ActiveRecord::Base
77
+ # Any raw SQL doing a join or sub-select on public_comments, if it want to be representative,
78
+ # must repeat "public = true".
79
+ has_many :public_comments, -> { where(public: true) }, class_name: 'Comment'
80
+ end
81
+
82
+ class Comment < ActiveRecord::Base
83
+ # Any raw SQL doing a join or sub-select to this model, if it want to be representative,
84
+ # must repeat "deleted_at IS NULL".
85
+ default_scope -> { where(deleted_at: nil) }
86
+ end
87
+ ```
88
+
89
+ All of this is avoided by where_assoc_* methods.
90
+
91
+ ### Unable to handle recursive associations
92
+
93
+ When you have recursive associations such as parent/children, you must compare multiple rows of the same table. To do this, you have no choice but to write your own raw SQL to, at the very least, do a SQL join with an alias.
94
+
95
+ This brings us back to the [raw SQL joins](#raw-sql-joins-or-sub-selects) problem.
96
+
97
+ `where_assoc_*` methods handle this seemlessly.
98
+
99
+ ## ActiveRecord only
100
+
101
+ Those are the common ways given in stack overflow answers.
102
+
103
+ ### Using `joins` and `where`
104
+
105
+ ```ruby
106
+ Post.where_assoc_exists(:comments, is_spam: true)
107
+ Post.joins(:comments).where(comments: {is_spam: true})
108
+ ```
109
+
110
+ * If the association maps to multiple records (such as with a has_many), then the the relation will return one record for each matching association record. In this example, you would get the same post twice if it has 2 comments that are marked as spam.
111
+ Using `uniq` can solve this issue, but if you do that in a scope, then that scope unexpectedly adds a DISTINCT to your query, which can lead to unexpected results if you actually wanted duplicates for a different reason.
112
+
113
+ * Doing the opposite is a lot more complicated, as seen below. You have to include your conditions directly in the join and use a LEFT JOIN, this means writing the whole thing in raw SQL, and then you must check for the id of the association to be empty.
114
+
115
+ ```ruby
116
+ Post.where_assoc_not_exists(:comments, is_spam: true)
117
+ Post.joins("LEFT JOIN comments ON posts.id = comments.post_id AND comments.is_spam = true").where(comments: {id: nil})
118
+ ```
119
+
120
+ Writing a raw join like that has yet more problems: [raw SQL joins](#raw-sql-joins-or-sub-selects)
121
+
122
+ * If you want to have another condition referring to the same association (or just the same table), then you need to write out the SQL for the second join using an alias. Therefore, your scopes are not even compatible unless each of them has a join with a unique alias.
123
+
124
+ ```ruby
125
+ # We want to be able to match either different or the same records
126
+ Post.where_assoc_exists(:comments, is_spam: true)
127
+ .where_assoc_exists(:comments, is_reported: true)
128
+
129
+ # Please don't ever do this, this just shows how painful it would be
130
+ # If you reach the need to do this but won't use where_assoc_exists,
131
+ # go for a regular #where("EXISTS( SELECT ...)")
132
+ Post.joins(:comments).where(comments: {is_spam: true})
133
+ .joins("JOIN comments comments_for_reported ON posts.id = comments_for_reported.post_id")
134
+ .where(comments_for_reported: {is_reported: true})
135
+ ```
136
+
137
+ * Cannot be used with Rails 5's `or` unless both side do the same `joins`.
138
+ * [Treats has_one like a has_many](#treating-has_one-like-has_many)
139
+ * [Can't handle recursive associations](#unable-to-handle-recursive-associations)
140
+
141
+ ### Using `includes` (or `eager_load`) and `where`
142
+
143
+ This solution is similar to the `joins` one above, but avoids the need for `uniq`. Every other problems of the `joins` remain. You also add other potential issues.
144
+
145
+ ```ruby
146
+ Post.where_assoc_exists(:comments, is_spam: true)
147
+ Post.eager_load(:comments).where(comments: {is_spam: true})
148
+ ```
149
+
150
+ * You are triggering the loading of potentially lots of records that you might not need. You don't expect a scope like `have_reported_comments` to trigger eager loading. This is a performance degradation.
151
+
152
+ * The eager loaded records of the association are actually also filtered by the conditions. All of the posts returned will only have the comments that are spam.
153
+ This means if you iterate on `Post.have_reported_comments` to display each of the comments of the posts that have at least one reported comment, you are actually only going to display the reported comments. This may be what you wanted to do, but it clearly isn't intuitive.
154
+
155
+ * Cannot be used with Rails 5's `or` unless both side do the same `includes` or `eager_load`.
156
+
157
+ * [Treats has_one like a has_many](#treating-has_one-like-has_many)
158
+ * [Can't handle recursive associations](#unable-to-handle-recursive-associations)
159
+
160
+ * Simply cannot be used for complex cases.
161
+
162
+ Note: using `includes` (or `eager_load`) already does a LEFT JOIN, so it is pretty easy to do a "not exists", but only if you don't need any condition on the association (which would normally need to be in the JOIN clause):
163
+
164
+ ```ruby
165
+ Post.where_assoc_exists(:comments)
166
+ Post.eager_load(:comments).where(comments: {id: nil})
167
+ ```
168
+
169
+ ### Using `where("EXISTS( SELECT... )")`
170
+
171
+ This is what is gem does behind the scene, but doing it manually can lead to troubles:
172
+
173
+ * Problems with writing [raw SQL sub-selects](#raw-sql-joins-or-sub-selects)
174
+
175
+ * Unless you do a quite complex nested sub-selects, you will [treat has_one like a has_many](#treating-has_one-like-has_many)
176
+
177
+
178
+ ## Gems
179
+
180
+ ### where_exists
181
+
182
+ https://github.com/EugZol/where_exists
183
+
184
+ An interesting gem that also does `EXISTS (SELECT ... )`behind the scene. Solves most issues from ActiveRecord only alternatives, but appears less powerful than where_assoc_exists.
185
+
186
+ * where_exists supports polymorphic belongs_to. This is something that where_assoc doesn't do at the moment.
187
+ However, the way it does this is by doing a pluck on the type column, which in some situation could be a slow query if there is a lots of rows to scan.
188
+
189
+ * Unable to use scopes of the association's model.
190
+ ```ruby
191
+ # There is no equivalent for this (admins is a scope on User)
192
+ Comment.where_assoc_exists(:author, &:admins)
193
+ ```
194
+
195
+ * Cannot use a block for more complex conditions
196
+ ```ruby
197
+ # There is no equivalent for this
198
+ Comment.where_assoc_exists(:author) { admins.where("created_at <= ?", 1.month.ago) }
199
+ ```
200
+
201
+ * Unable to dig deeper in the associations
202
+ Note: it does follow :through associations so doing a custom associations for your need can be a workaround.
203
+
204
+ ```ruby
205
+ # There is no equivalent for this (Users that have posts with at least a comments)
206
+ User.where_assoc_exists([:posts, :comments])
207
+ ```
208
+
209
+ * Has no equivalent to `where_assoc_count`
210
+ ```ruby
211
+ # There is no equivalent for this (posts with more than 5 comments)
212
+ Post.where_assoc_count(:comments, :>, 5)
213
+ ```
214
+
215
+ * [Treats has_one like a has_many](#treating-has_one-like-has_many)
216
+
217
+ * [Can't handle recursive associations](#unable-to-handle-recursive-associations)
218
+
219
+ * `where_exists` is shorter than `where_assoc_exists`, but it is also less obvious about what it does.
220
+ In any case, it is trivial to alias one name to the other one.
221
+
222
+ * where_exists supports Rails 4.2 and up, while where_assoc supports Rails 4.1 and up.
@@ -0,0 +1,222 @@
1
+ Here are some example usages of the gem, along with the generated SQL. Each of those can be chained with scoping methods.
2
+
3
+ Models can be found in [examples/models.md](examples/models.md). Explanation is provided in that file to be able to run these queries.
4
+
5
+ The content below is generated from running `ruby examples/examples.rb`
6
+
7
+ -------
8
+
9
+ ## Simple examples
10
+
11
+ ```ruby
12
+ # Posts that have a least one comment
13
+ Post.where_assoc_exists(:comments)
14
+ ```
15
+ ```sql
16
+ SELECT "posts".* FROM "posts"
17
+ WHERE (EXISTS (
18
+ SELECT 0 FROM "comments"
19
+ WHERE "comments"."post_id" = "posts"."id"
20
+ ))
21
+ ```
22
+
23
+ ---
24
+
25
+ ```ruby
26
+ # Posts that have no comments
27
+ Post.where_assoc_not_exists(:comments)
28
+ ```
29
+ ```sql
30
+ SELECT "posts".* FROM "posts"
31
+ WHERE (NOT EXISTS (
32
+ SELECT 0 FROM "comments"
33
+ WHERE "comments"."post_id" = "posts"."id"
34
+ ))
35
+ ```
36
+
37
+ ---
38
+
39
+ ```ruby
40
+ # Posts that have a least 50 comment
41
+ Post.where_assoc_count(50, :<=, :comments)
42
+ ```
43
+ ```sql
44
+ SELECT "posts".* FROM "posts"
45
+ WHERE ((50) <= COALESCE((
46
+ SELECT COUNT(*) FROM "comments"
47
+ WHERE "comments"."post_id" = "posts"."id"
48
+ ), 0))
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Examples with condition / scope
54
+
55
+ ```ruby
56
+ # comments of `my_post` that were made by an admin (Using a hash)
57
+ my_post.comments.where_assoc_exists(:author, is_admin: true)
58
+ ```
59
+ ```sql
60
+ SELECT "comments".* FROM "comments"
61
+ WHERE "comments"."post_id" = 1 AND (EXISTS (
62
+ SELECT 0 FROM "users"
63
+ WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
64
+ ))
65
+ ```
66
+
67
+ ---
68
+
69
+ ```ruby
70
+ # comments of `my_post` that were not made by an admin (Using scope)
71
+ my_post.comments.where_assoc_not_exists(:author, &:admins)
72
+ ```
73
+ ```sql
74
+ SELECT "comments".* FROM "comments"
75
+ WHERE "comments"."post_id" = 1 AND (NOT EXISTS (
76
+ SELECT 0 FROM "users"
77
+ WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
78
+ ))
79
+ ```
80
+
81
+ ---
82
+
83
+ ```ruby
84
+ # Posts that have at least 5 reported comments (Using array condition)
85
+ Post.where_assoc_count(5, :<=, :comments, ["is_reported = ?", true])
86
+ ```
87
+ ```sql
88
+ SELECT "posts".* FROM "posts"
89
+ WHERE ((5) <= COALESCE((
90
+ SELECT COUNT(*) FROM "comments"
91
+ WHERE "comments"."post_id" = "posts"."id" AND (is_reported = 't')
92
+ ), 0))
93
+ ```
94
+
95
+ ---
96
+
97
+ ```ruby
98
+ # Posts made by an admin (Using a string)
99
+ Post.where_assoc_exists(:author, "is_admin = 't'")
100
+ ```
101
+ ```sql
102
+ SELECT "posts".* FROM "posts"
103
+ WHERE (EXISTS (
104
+ SELECT 0 FROM "users"
105
+ WHERE "users"."id" = "posts"."author_id" AND (is_admin = 't')
106
+ ))
107
+ ```
108
+
109
+ ---
110
+
111
+ ```ruby
112
+ # comments of `my_post` that were not made by an admin (Using block and a scope)
113
+ my_post.comments.where_assoc_not_exists(:author) { admins }
114
+ ```
115
+ ```sql
116
+ SELECT "comments".* FROM "comments"
117
+ WHERE "comments"."post_id" = 1 AND (NOT EXISTS (
118
+ SELECT 0 FROM "users"
119
+ WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
120
+ ))
121
+ ```
122
+
123
+ ---
124
+
125
+ ```ruby
126
+ # Posts that have at least 5 reported comments (Using block with #where)
127
+ Post.where_assoc_count(5, :<=, :comments) { where(is_reported: true) }
128
+ ```
129
+ ```sql
130
+ SELECT "posts".* FROM "posts"
131
+ WHERE ((5) <= COALESCE((
132
+ SELECT COUNT(*) FROM "comments"
133
+ WHERE "comments"."post_id" = "posts"."id" AND "comments"."is_reported" = 't'
134
+ ), 0))
135
+ ```
136
+
137
+ ---
138
+
139
+ ```ruby
140
+ # comments made in replies to my_user's post
141
+ Comment.where_assoc_exists(:post, author_id: my_user.id)
142
+ ```
143
+ ```sql
144
+ SELECT "comments".* FROM "comments"
145
+ WHERE (EXISTS (
146
+ SELECT 0 FROM "posts"
147
+ WHERE "posts"."id" = "comments"."post_id" AND "posts"."author_id" = 1
148
+ ))
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Complex / powerful examples
154
+
155
+ ```ruby
156
+ # posts with a comment by an admin (uses array to go through multiple associations)
157
+ Post.where_assoc_exists([:comments, :author], is_admin: true)
158
+ ```
159
+ ```sql
160
+ SELECT "posts".* FROM "posts"
161
+ WHERE (EXISTS (
162
+ SELECT 0 FROM "comments"
163
+ WHERE "comments"."post_id" = "posts"."id" AND (EXISTS (
164
+ SELECT 0 FROM "users"
165
+ WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
166
+ ))
167
+ ))
168
+ ```
169
+
170
+ ---
171
+
172
+ ```ruby
173
+ # posts where the author also commented on the post (use conditions between posts)
174
+ Post.where_assoc_exists(:comments, "posts.author_id = comments.author_id")
175
+ ```
176
+ ```sql
177
+ SELECT "posts".* FROM "posts"
178
+ WHERE (EXISTS (
179
+ SELECT 0 FROM "comments"
180
+ WHERE "comments"."post_id" = "posts"."id" AND (posts.author_id = comments.author_id)
181
+ ))
182
+ ```
183
+
184
+ ---
185
+
186
+ ```ruby
187
+ # posts with a reported comment made by an admin (must be the same comments)
188
+ Post.where_assoc_exists(:comments, is_reported: true) {
189
+ where_assoc_exists(:author, is_admin: true)
190
+ }
191
+ ```
192
+ ```sql
193
+ SELECT "posts".* FROM "posts"
194
+ WHERE (EXISTS (
195
+ SELECT 0 FROM "comments"
196
+ WHERE "comments"."post_id" = "posts"."id" AND "comments"."is_reported" = 't' AND (EXISTS (
197
+ SELECT 0 FROM "users"
198
+ WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
199
+ ))
200
+ ))
201
+ ```
202
+
203
+ ---
204
+
205
+ ```ruby
206
+ # posts with a reported comment and a comment by an admin (can be different or same comments)
207
+ my_user.posts.where_assoc_exists(:comments, is_reported: true)
208
+ .where_assoc_exists([:comments, :author], is_admin: true)
209
+ ```
210
+ ```sql
211
+ SELECT "posts".* FROM "posts"
212
+ WHERE "posts"."author_id" = 1 AND (EXISTS (
213
+ SELECT 0 FROM "comments"
214
+ WHERE "comments"."post_id" = "posts"."id" AND "comments"."is_reported" = 't'
215
+ )) AND (EXISTS (
216
+ SELECT 0 FROM "comments"
217
+ WHERE "comments"."post_id" = "posts"."id" AND (EXISTS (
218
+ SELECT 0 FROM "users"
219
+ WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
220
+ ))
221
+ ))
222
+ ```
data/README.md CHANGED
@@ -15,14 +15,16 @@ my_post.comments.where_assoc_not_exists(:author, is_admin: true).where(...)
15
15
  Post.where_assoc_exists([:comments, :author], &:admins).where(...)
16
16
 
17
17
  # Find my_user's posts that have at least 5 non-spam comments
18
- my_user.posts.where_assoc_count(5, :>=, :comments) { |comments| comments.where(spam: false) }.where(...)
18
+ my_user.posts.where_assoc_count(5, :>=, :comments) { |comments| comments.where(is_spam: false) }.where(...)
19
19
  ```
20
20
 
21
21
  These allow for powerful, chainable, clear and easy to reuse queries. (Great for scopes)
22
22
 
23
23
  You also avoid many [problems with the alternative options](ALTERNATIVES_PROBLEMS.md).
24
24
 
25
- Works with SQLite3, PostgreSQL and MySQL. [MySQL has one limitation](#mysql-doesnt-support-sub-limit). Untested with other DBMS.
25
+ Works with SQLite3, PostgreSQL and MySQL. [MySQL has one limitation](#mysql-doesnt-support-sub-limit). Untested with other RDBMS.
26
+
27
+ Here are [many examples](EXAMPLES.md), including the generated SQL queries.
26
28
 
27
29
  ## Feedback
28
30
 
@@ -60,13 +62,13 @@ Or install it yourself as:
60
62
  Returns a new relation, which is the result of filtering the current relation based on if a record for the specified association of the model exists (or not). Conditions that the associated model must match to count as existing can also be specified.
61
63
 
62
64
  ```ruby
63
- Post.where_assoc_exists(:comments, spam: true)
64
- Post.where_assoc_not_exists(:comments, spam: true)
65
+ Post.where_assoc_exists(:comments, is_spam: true)
66
+ Post.where_assoc_not_exists(:comments, is_spam: true)
65
67
  ```
66
68
 
67
69
  * 1st parameter: the association we are doing the condition on.
68
70
  * 2nd parameter: (optional) the condition to apply on the association. It can be anything that `#where` can receive, so: Hash, String and Array (string with binds).
69
- * 3rd parameter: [options (listed below)](#options) to alter some behaviors.
71
+ * 3rd parameter: [options (listed below)](#options) to alter some behaviors. (rarely necessary)
70
72
  * block: adds more complex conditions by receiving a relation on the association. Can apply `#where`, `#where_assoc_*`, scopes, and other scoping methods.
71
73
  The block either:
72
74
 
@@ -81,11 +83,11 @@ Post.where_assoc_not_exists(:comments, spam: true)
81
83
  This is a generalization of `#where_assoc_exists` and `#where_assoc_not_exists`. It behave behaves the same way as them, but is more flexible as it allows you to be specific about how many matches there should be. To clarify, here are equivalent examples:
82
84
 
83
85
  ```ruby
84
- Post.where_assoc_exists(:comments, spam: true)
85
- Post.where_assoc_count(1, :<=, :comments, spam: true)
86
+ Post.where_assoc_exists(:comments, is_spam: true)
87
+ Post.where_assoc_count(1, :<=, :comments, is_spam: true)
86
88
 
87
- Post.where_assoc_not_exists(:comments, spam: true)
88
- Post.where_assoc_count(0, :==, :comments, spam: true)
89
+ Post.where_assoc_not_exists(:comments, is_spam: true)
90
+ Post.where_assoc_count(0, :==, :comments, is_spam: true)
89
91
  ```
90
92
 
91
93
  * 1st parameter: a number or any string of SQL to embed in the query used for the leftoperand of the comparison.
@@ -99,6 +101,10 @@ The order of the parameters may seem confusing, but you will get used to it. To
99
101
 
100
102
  The parameters are in the same order as in that query: number, operator, association.
101
103
 
104
+ ### More examples
105
+
106
+ You can view [more usage examples](EXAMPLES.md).
107
+
102
108
  ### Options
103
109
 
104
110
  Each of the methods above can take an options argument. It is also possible to change the default value for the options.
@@ -153,38 +159,6 @@ These methods have many advantages over the alternative ways of achieving the si
153
159
  * Handles recursive associations (such as parent/children) seemlessly.
154
160
  * Can be used to quickly generate a SQL query that you can edit/use manually.
155
161
 
156
- ## More examples
157
-
158
- High level explanation of various ways of using the methods. Also take a look at [usage tips](#usage-tips)
159
-
160
- ```ruby
161
- # Find my_post's comments that were not made by an admin
162
- # Uses a Hash for the condition
163
- my_post.comments.where_assoc_not_exists(:author, is_admin: true)
164
-
165
- # Find my_user's posts that have comments by an admin
166
- # Uses an array as shortcut to go to a nested related
167
- # Uses the block shortcut to use a scope that exists on Author
168
- my_user.posts.where_assoc_exists([:comments, :author], &:admins).where(...)
169
-
170
- # Find my_user's posts that have at least 5 non-spam comments
171
- # Uses a block with a parameter to do a condition
172
- my_user.posts.where_assoc_count(5, :>=, :comments) { |s| s.where(spam: false) }
173
-
174
- # Find my_user's posts that have at least 5 non-spam comments
175
- # Uses a block without parameters to do a condition
176
- my_user.posts.where_assoc_count(5, :>=, :comments) { where(spam: false) }
177
-
178
- # Find my_user's posts that have comments by an honest admin
179
- # Uses multiple associations.
180
- # Uses a hash as 2nd parameter to do the conditions
181
- my_user.posts.where_assoc_exists([:comments, :author], honest: true, is_admin: true)
182
-
183
- # Find any post that has reached its maximum number of allowed comments
184
- # Uses a string on the left side (first parameter) to refer to a column in the previous table.
185
- Post.where_assoc_count("posts.max_comments_allowed", :==, :comments)
186
- ```
187
-
188
162
  ## Usage tips
189
163
 
190
164
  ### Nested associations
@@ -216,10 +190,10 @@ This shortcut can be used for every `where_assoc_*` methods. The conditions and
216
190
  The following have different meanings:
217
191
 
218
192
  ```ruby
219
- my_user.posts.where_assoc_exists(:comments_authors, is_admin: true, honest: true)
193
+ my_user.posts.where_assoc_exists(:comments_authors, is_admin: true, is_honest: true)
220
194
 
221
195
  my_user.posts.where_assoc_exists(:comments_authors, is_admin: true)
222
- .where_assoc_exists(:comments_authors, honest: true)
196
+ .where_assoc_exists(:comments_authors, is_honest: true)
223
197
  ```
224
198
 
225
199
  The first is the posts of `my_user` that have a comment made by an honest admin. It requires a single comment to match every conditions.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record_where_assoc/version"
3
+ require_relative "active_record_where_assoc/version"
4
4
  require "active_record"
5
5
 
6
6
  module ActiveRecordWhereAssoc
@@ -13,9 +13,9 @@ module ActiveRecordWhereAssoc
13
13
  end
14
14
  end
15
15
 
16
- require "active_record_where_assoc/core_logic"
17
- require "active_record_where_assoc/query_methods"
18
- require "active_record_where_assoc/querying"
16
+ require_relative "active_record_where_assoc/core_logic"
17
+ require_relative "active_record_where_assoc/query_methods"
18
+ require_relative "active_record_where_assoc/querying"
19
19
 
20
20
  ActiveSupport.on_load(:active_record) do
21
21
  ActiveRecord.eager_load!
@@ -55,13 +55,13 @@ module ActiveRecordWhereAssoc
55
55
  #
56
56
  # # Posts that have at least one comment considered as spam
57
57
  # # Using a Hash
58
- # Post.where_assoc_exists(:comments, spam_flag: true)
58
+ # Post.where_assoc_exists(:comments, is_spam: true)
59
59
  #
60
60
  # # Using a String
61
- # Post.where_assoc_exists(:comments, "spam_flag = true")
61
+ # Post.where_assoc_exists(:comments, "is_spam = true")
62
62
  #
63
63
  # # Using an Array (a string and its binds)
64
- # Post.where_assoc_exists(:comments, ["spam_flag = ?", true])
64
+ # Post.where_assoc_exists(:comments, ["is_spam = ?", true])
65
65
  #
66
66
  # If the condition argument is blank, it is ignored (just like #where does).
67
67
  #
@@ -94,7 +94,7 @@ module ActiveRecordWhereAssoc
94
94
  # filters or may return nil to do nothing.
95
95
  #
96
96
  # # Using a where for the added condition
97
- # Post.where_assoc_exists(:comments) { |comments| comments.where(spam_flag: true) }
97
+ # Post.where_assoc_exists(:comments) { |comments| comments.where(is_spam: true) }
98
98
  #
99
99
  # # Applying a scope of the relation
100
100
  # Post.where_assoc_exists(:comments) { |comments| comments.spam_flagged }
@@ -108,7 +108,7 @@ module ActiveRecordWhereAssoc
108
108
  # the block. Everything else is identical to the block with one argument.
109
109
  #
110
110
  # # Using a where for the added condition
111
- # Post.where_assoc_exists(:comments) { where(spam_flag: true) }
111
+ # Post.where_assoc_exists(:comments) { where(is_spam: true) }
112
112
  #
113
113
  # # Applying a scope of the relation
114
114
  # Post.where_assoc_exists(:comments) { spam_flagged }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordWhereAssoc
4
- VERSION = "0.1.0".freeze
4
+ VERSION = "0.1.1".freeze
5
5
  end
@@ -1,6 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Just in case of typo in the require. Call the right one automatically
4
-
5
-
6
4
  require_relative "active_record_where_assoc"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord_where_assoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maxime Handfield Lapointe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-14 00:00:00.000000000 Z
11
+ date: 2018-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: niceql
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '='
130
+ - !ruby/object:Gem::Version
131
+ version: 0.1.14
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '='
137
+ - !ruby/object:Gem::Version
138
+ version: 0.1.14
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: sqlite3
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -144,6 +158,8 @@ executables: []
144
158
  extensions: []
145
159
  extra_rdoc_files: []
146
160
  files:
161
+ - ALTERNATIVES_PROBLEMS.md
162
+ - EXAMPLES.md
147
163
  - LICENSE.txt
148
164
  - README.md
149
165
  - lib/active_record_where_assoc.rb