inquery 0.0.1

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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.releaser_config +4 -0
  4. data/.rubocop.yml +42 -0
  5. data/.travis.yml +9 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +21 -0
  9. data/README.md +288 -0
  10. data/RUBY_VERSION +1 -0
  11. data/Rakefile +36 -0
  12. data/VERSION +1 -0
  13. data/doc/Inquery.html +119 -0
  14. data/doc/Inquery/Exceptions.html +115 -0
  15. data/doc/Inquery/Exceptions/Base.html +127 -0
  16. data/doc/Inquery/Exceptions/InvalidRelation.html +131 -0
  17. data/doc/Inquery/Exceptions/UnknownCallSignature.html +131 -0
  18. data/doc/Inquery/Mixins.html +117 -0
  19. data/doc/Inquery/Mixins/RelationValidation.html +334 -0
  20. data/doc/Inquery/Mixins/RelationValidation/ClassMethods.html +190 -0
  21. data/doc/Inquery/Mixins/SchemaValidation.html +124 -0
  22. data/doc/Inquery/Mixins/SchemaValidation/ClassMethods.html +192 -0
  23. data/doc/Inquery/Query.html +736 -0
  24. data/doc/Inquery/Query/Chainable.html +476 -0
  25. data/doc/_index.html +254 -0
  26. data/doc/class_list.html +58 -0
  27. data/doc/css/common.css +1 -0
  28. data/doc/css/full_list.css +57 -0
  29. data/doc/css/style.css +339 -0
  30. data/doc/file.README.html +365 -0
  31. data/doc/file_list.html +60 -0
  32. data/doc/frames.html +26 -0
  33. data/doc/index.html +365 -0
  34. data/doc/js/app.js +219 -0
  35. data/doc/js/full_list.js +181 -0
  36. data/doc/js/jquery.js +4 -0
  37. data/doc/method_list.html +147 -0
  38. data/doc/top-level-namespace.html +112 -0
  39. data/inquery.gemspec +58 -0
  40. data/lib/inquery.rb +10 -0
  41. data/lib/inquery/exceptions.rb +7 -0
  42. data/lib/inquery/mixins/relation_validation.rb +100 -0
  43. data/lib/inquery/mixins/schema_validation.rb +27 -0
  44. data/lib/inquery/query.rb +50 -0
  45. data/lib/inquery/query/chainable.rb +53 -0
  46. data/test/db/models.rb +20 -0
  47. data/test/db/schema.rb +20 -0
  48. data/test/inquery/query/chainable_test.rb +67 -0
  49. data/test/inquery/query_test.rb +47 -0
  50. data/test/queries/group/fetch_as_json.rb +13 -0
  51. data/test/queries/group/fetch_green.rb +11 -0
  52. data/test/queries/group/fetch_red.rb +11 -0
  53. data/test/queries/group/filter_with_color.rb +12 -0
  54. data/test/queries/user/fetch_all.rb +9 -0
  55. data/test/queries/user/fetch_in_group.rb +13 -0
  56. data/test/queries/user/fetch_in_group_rel.rb +17 -0
  57. data/test/test_helper.rb +26 -0
  58. metadata +265 -0
@@ -0,0 +1,365 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6
+ <title>
7
+ File: README
8
+
9
+ &mdash; Documentation by YARD 0.8.7.6
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ hasFrames = window.top.frames.main ? true : false;
19
+ relpath = '';
20
+ framesUrl = "frames.html#!file.README.html";
21
+ </script>
22
+
23
+
24
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
25
+
26
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
27
+
28
+
29
+ </head>
30
+ <body>
31
+ <div id="header">
32
+ <div id="menu">
33
+
34
+ <a href="_index.html">Index</a> &raquo;
35
+ <span class="title">File: README</span>
36
+
37
+
38
+ <div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
39
+ </div>
40
+
41
+ <div id="search">
42
+
43
+ <a class="full_list_link" id="class_list_link"
44
+ href="class_list.html">
45
+ Class List
46
+ </a>
47
+
48
+ <a class="full_list_link" id="method_list_link"
49
+ href="method_list.html">
50
+ Method List
51
+ </a>
52
+
53
+ <a class="full_list_link" id="file_list_link"
54
+ href="file_list.html">
55
+ File List
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <iframe id="search_frame"></iframe>
63
+
64
+ <div id="content"><div id='filecontents'>
65
+ <h1 id="label-Inquery">Inquery</h1>
66
+
67
+ <p>A skeleton that allows extracting queries into atomic, reusable classes.</p>
68
+
69
+ <h2 id="label-Installation">Installation</h2>
70
+
71
+ <p>To install the <strong>Inquery</strong> gem:</p>
72
+
73
+ <pre class="code ruby"><code class="ruby">$ gem install inquery</code></pre>
74
+
75
+ <p>To install it using <code>bundler</code> (recommended for any application),
76
+ add it to your <code>Gemfile</code>:</p>
77
+
78
+ <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_gem'>gem</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>inquery</span><span class='tstring_end'>&#39;</span></span>
79
+ </code></pre>
80
+
81
+ <h2 id="label-Basic+usage">Basic usage</h2>
82
+
83
+ <pre class="code ruby"><code class="ruby"><span class='kw'>class</span> <span class='const'>FetchUsersWithACar</span> <span class='op'>&lt;</span> <span class='const'>Inquery</span><span class='op'>::</span><span class='const'>Query</span>
84
+ <span class='id identifier rubyid_schema'>schema</span><span class='lparen'>(</span>
85
+ <span class='label'>color:</span> <span class='symbol'>:symbol</span>
86
+ <span class='rparen'>)</span>
87
+
88
+ <span class='kw'>def</span> <span class='id identifier rubyid_call'>call</span>
89
+ <span class='const'>User</span><span class='period'>.</span><span class='id identifier rubyid_joins'>joins</span><span class='lparen'>(</span><span class='symbol'>:cars</span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_where'>where</span><span class='lparen'>(</span><span class='label'>cars:</span> <span class='lbrace'>{</span> <span class='label'>color:</span> <span class='id identifier rubyid_osparams'>osparams</span><span class='period'>.</span><span class='id identifier rubyid_color'>color</span> <span class='rbrace'>}</span><span class='rparen'>)</span>
90
+ <span class='kw'>end</span>
91
+ <span class='kw'>end</span>
92
+
93
+ <span class='const'>FetchUsersWithACar</span><span class='period'>.</span><span class='id identifier rubyid_run'>run</span>
94
+ <span class='comment'># =&gt; [&lt;User id: 1 ...]
95
+ </span></code></pre>
96
+
97
+ <p>Inquery offers its functionality trough two query base classes:
98
+ <span class='object_link'><a href="Inquery/Query.html" title="Inquery::Query (class)">Inquery::Query</a></span> and <span class='object_link'><a href="Inquery/Query/Chainable.html" title="Inquery::Query::Chainable (class)">Inquery::Query::Chainable</a></span>. See the following
99
+ sections for detailed explanations.</p>
100
+
101
+ <h2 id="label-Basic+queries">Basic queries</h2>
102
+
103
+ <p>Basic queries inherit from <span class='object_link'><a href="Inquery/Query.html" title="Inquery::Query (class)">Inquery::Query</a></span>. They receive an optional set
104
+ of parameters and commonly return a relation / AR result. An optional
105
+ <code>process</code> method lets you perform additional result processing
106
+ steps if needed (i.e. converting the result to a hash or similar).</p>
107
+
108
+ <p>For this basic functionality, inherit from <span class='object_link'><a href="Inquery/Query.html" title="Inquery::Query (class)">Inquery::Query</a></span> and overwrite
109
+ the <code>call</code> and optionally the <code>process</code> method:</p>
110
+
111
+ <pre class="code ruby"><code class="ruby"><span class='kw'>class</span> <span class='const'>FetchRedCarsAsJson</span>
112
+ <span class='comment'># The `call` method must be overwritten for every query. It is usually called
113
+ </span> <span class='comment'># via `run`.
114
+ </span> <span class='kw'>def</span> <span class='id identifier rubyid_call'>call</span>
115
+ <span class='const'>Car</span><span class='period'>.</span><span class='id identifier rubyid_where'>where</span><span class='lparen'>(</span><span class='label'>color:</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>red</span><span class='tstring_end'>&#39;</span></span><span class='rparen'>)</span>
116
+ <span class='kw'>end</span>
117
+
118
+ <span class='comment'># The `process` method can optionally be overwritten. The base implementation
119
+ </span> <span class='comment'># just returns the unprocessed `results` argument.
120
+ </span> <span class='kw'>def</span> <span class='id identifier rubyid_process'>process</span><span class='lparen'>(</span><span class='id identifier rubyid_results'>results</span><span class='rparen'>)</span>
121
+ <span class='id identifier rubyid_results'>results</span><span class='period'>.</span><span class='id identifier rubyid_to_json'>to_json</span>
122
+ <span class='kw'>end</span>
123
+ <span class='kw'>end</span>
124
+ </code></pre>
125
+
126
+ <p>Queries can be called in various ways:</p>
127
+
128
+ <pre class="code ruby"><code class="ruby"><span class='comment'># Instantiates the query class and runs `call` and `process`.
129
+ </span><span class='const'>FetchRedCarsAsJson</span><span class='period'>.</span><span class='id identifier rubyid_run'>run</span><span class='lparen'>(</span><span class='id identifier rubyid_params'>params</span> <span class='op'>=</span> <span class='lbrace'>{</span><span class='rbrace'>}</span><span class='rparen'>)</span>
130
+
131
+ <span class='comment'># Instantiates the query class and runs `call`. No result processing
132
+ </span><span class='comment'># is done.
133
+ </span><span class='const'>FetchRedCarsAsJson</span><span class='period'>.</span><span class='id identifier rubyid_call'>call</span><span class='lparen'>(</span><span class='id identifier rubyid_params'>params</span> <span class='op'>=</span> <span class='lbrace'>{</span><span class='rbrace'>}</span><span class='rparen'>)</span>
134
+
135
+ <span class='comment'># You can also instantiate the query class manually.
136
+ </span><span class='const'>FetchRedCarsAsJson</span><span class='period'>.</span><span class='id identifier rubyid_new'>new</span><span class='lparen'>(</span><span class='id identifier rubyid_params'>params</span> <span class='op'>=</span> <span class='lbrace'>{</span><span class='rbrace'>}</span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_run'>run</span>
137
+
138
+ <span class='comment'># Or just run the `call` method without `process`.
139
+ </span><span class='const'>FetchRedCarsAsJson</span><span class='period'>.</span><span class='id identifier rubyid_new'>new</span><span class='lparen'>(</span><span class='id identifier rubyid_params'>params</span> <span class='op'>=</span> <span class='lbrace'>{</span><span class='rbrace'>}</span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_call'>call</span>
140
+ </code></pre>
141
+
142
+ <p>Note that it&#39;s perfectly fine for some queries to return
143
+ <code>nil</code>, i.e. if they&#39;re writing queries that don&#39;t fetch
144
+ any results.</p>
145
+
146
+ <h2 id="label-Chainable+queries">Chainable queries</h2>
147
+
148
+ <p>Chainable queries are queries that input and output an Active Record
149
+ relation. You can access the given relation using the method
150
+ <code>relation</code>:</p>
151
+
152
+ <pre class="code ruby"><code class="ruby"><span class='kw'>class</span> <span class='const'>Queries</span><span class='op'>::</span><span class='const'>User</span><span class='op'>::</span><span class='const'>FetchActive</span> <span class='op'>&lt;</span> <span class='const'>Inquery</span><span class='op'>::</span><span class='const'>Query</span><span class='op'>::</span><span class='const'>Chainable</span>
153
+ <span class='kw'>def</span> <span class='id identifier rubyid_call'>call</span>
154
+ <span class='id identifier rubyid_relation'>relation</span><span class='period'>.</span><span class='id identifier rubyid_where'>where</span><span class='lparen'>(</span><span class='label'>active:</span> <span class='int'>1</span><span class='rparen'>)</span>
155
+ <span class='kw'>end</span>
156
+ <span class='kw'>end</span>
157
+ </code></pre>
158
+
159
+ <p>Input and output relations may or may not be of the same AR class (i.e. you
160
+ could pass a relation of <code>Group</code>s and receive back a relation of
161
+ corresponding <code>User</code>s).</p>
162
+
163
+ <h3 id="label-Relation+validation">Relation validation</h3>
164
+
165
+ <p>Chainable queries allow you to further specify and validate the relation it
166
+ receives. This is done using the static <code>relation</code> method:</p>
167
+
168
+ <pre class="code ruby"><code class="ruby"><span class='kw'>class</span> <span class='const'>Queries</span><span class='op'>::</span><span class='const'>User</span><span class='op'>::</span><span class='const'>FetchActive</span> <span class='op'>&lt;</span> <span class='const'>Inquery</span><span class='op'>::</span><span class='const'>Query</span><span class='op'>::</span><span class='const'>Chainable</span>
169
+ <span class='comment'># This will raise an exception when passing a relation which does not
170
+ </span> <span class='comment'># correspond to the `User` model.
171
+ </span> <span class='id identifier rubyid_relation'>relation</span> <span class='label'>class:</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>User</span><span class='tstring_end'>&#39;</span></span>
172
+
173
+ <span class='comment'># ....
174
+ </span><span class='kw'>end</span>
175
+ </code></pre>
176
+
177
+ <p>The <code>relation</code> method accepts the following options:</p>
178
+ <ul><li>
179
+ <p><code>class</code></p>
180
+
181
+ <p>Allows to restrict the class (attribute <code>klass</code>) of the
182
+ relation. Use <code>nil</code> to not perform any checks. The
183
+ <code>class</code> attribute will also be taken to infer a default if no
184
+ relation is given and you didn&#39;t specify any <code>default</code>.</p>
185
+ </li><li>
186
+ <p><code>default</code></p>
187
+
188
+ <p>This allows to specify a default relation that will be taken if no relation
189
+ is given. This must be specified as a Proc returning the relation. Set this
190
+ to <code>false</code> for no default. If this is set to <code>nil</code>,
191
+ it will try to infer the default from the option <code>class</code> (if
192
+ given).</p>
193
+ </li><li>
194
+ <p><code>fields</code></p>
195
+
196
+ <p>Allows to restrict the number of fields / values the relation must select.
197
+ This is particularly useful if you&#39;re using the query as a subquery and
198
+ need it to return exactly one field. Use <code>nil</code> to not perform
199
+ any checks.</p>
200
+ </li><li>
201
+ <p><code>default_select</code></p>
202
+
203
+ <p>If this is set to a symbol, the relation does not have any select fields
204
+ specified (<code>select_values</code> is empty) and <code>fields</code> is
205
+ &gt; 0, it will automatically select the given field. This option defaults
206
+ to <code>:id</code>. Use <code>nil</code> to disable this behavior.</p>
207
+ </li></ul>
208
+
209
+ <h3 id="label-Using+query+classes+as+regular+scopes">Using query classes as regular scopes</h3>
210
+
211
+ <p>Chainable queries can also be used as regular AR model scopes:</p>
212
+
213
+ <pre class="code ruby"><code class="ruby"><span class='kw'>class</span> <span class='const'>User</span> <span class='op'>&lt;</span> <span class='const'>ActiveRecord</span><span class='op'>::</span><span class='const'>Base</span>
214
+ <span class='id identifier rubyid_scope'>scope</span> <span class='symbol'>:active</span><span class='comma'>,</span> <span class='const'>Queries</span><span class='op'>::</span><span class='const'>User</span><span class='op'>::</span><span class='const'>FetchActive</span>
215
+ <span class='kw'>end</span>
216
+
217
+ <span class='kw'>class</span> <span class='const'>Queries</span><span class='op'>::</span><span class='const'>User</span><span class='op'>::</span><span class='const'>FetchActive</span> <span class='op'>&lt;</span> <span class='const'>Inquery</span><span class='op'>::</span><span class='const'>Query</span><span class='op'>::</span><span class='const'>Chainable</span>
218
+ <span class='comment'># Note that specifying either `class` or `default` is mandatory when using
219
+ </span> <span class='comment'># this query class as a scope. The reason for this is that, if the scope is
220
+ </span> <span class='comment'># otherwise empty, the class will receive `nil` from AR and therefore has no
221
+ </span> <span class='comment'># way of knowing which default class to take.
222
+ </span> <span class='id identifier rubyid_relation'>relation</span> <span class='label'>class:</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>User</span><span class='tstring_end'>&#39;</span></span>
223
+
224
+ <span class='kw'>def</span> <span class='id identifier rubyid_call'>call</span>
225
+ <span class='id identifier rubyid_relation'>relation</span><span class='period'>.</span><span class='id identifier rubyid_where'>where</span><span class='lparen'>(</span><span class='label'>active:</span> <span class='int'>1</span><span class='rparen'>)</span>
226
+ <span class='kw'>end</span>
227
+ <span class='kw'>end</span>
228
+ </code></pre>
229
+
230
+ <p>This approach allows to you use short and descriptive code like
231
+ <code>User.active</code> but have the possibly complex query code hidden in
232
+ a separate, reusable class.</p>
233
+
234
+ <p>Note that when using classes as scopes, the <code>process</code> method
235
+ will be ignored.</p>
236
+
237
+ <h3 id="label-Using+the+given+relation+as+subquery">Using the given relation as subquery</h3>
238
+
239
+ <p>In simple cases and all the examples above, we just extend the given
240
+ relation and return it again. It is also possible however to just use the
241
+ given relation as a subquery and return a completely new relation:</p>
242
+
243
+ <pre class="code ruby"><code class="ruby"><span class='kw'>class</span> <span class='const'>FetchUsersInGroup</span> <span class='op'>&lt;</span> <span class='const'>Inquery</span><span class='op'>::</span><span class='const'>Query</span><span class='op'>::</span><span class='const'>Chainable</span>
244
+ <span class='comment'># Here we do not specify any specific class, as we don&#39;t care for it as long
245
+ </span> <span class='comment'># as the relation returns exactly one field.
246
+ </span> <span class='id identifier rubyid_relation'>relation</span> <span class='label'>fields:</span> <span class='int'>1</span>
247
+
248
+ <span class='kw'>def</span> <span class='id identifier rubyid_call'>call</span>
249
+ <span class='kw'>return</span> <span class='op'>::</span><span class='const'>User</span><span class='period'>.</span><span class='id identifier rubyid_where'>where</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>%(</span><span class='tstring_content'>
250
+ id IN (
251
+ SELECT user_id FROM GROUPS_USERS WHERE group_id IN (
252
+ </span><span class='embexpr_beg'>#{</span><span class='id identifier rubyid_relation'>relation</span><span class='period'>.</span><span class='id identifier rubyid_to_sql'>to_sql</span><span class='embexpr_end'>}</span><span class='tstring_content'>
253
+ )
254
+ )
255
+ </span><span class='tstring_end'>)</span></span><span class='rparen'>)</span>
256
+ <span class='kw'>end</span>
257
+ <span class='kw'>end</span>
258
+ </code></pre>
259
+
260
+ <p>This query could then be called in the following ways:</p>
261
+
262
+ <pre class="code ruby"><code class="ruby"><span class='const'>FetchUsersInGroup</span><span class='period'>.</span><span class='id identifier rubyid_run'>run</span><span class='lparen'>(</span>
263
+ <span class='const'>GroupsUser</span><span class='period'>.</span><span class='id identifier rubyid_where'>where</span><span class='lparen'>(</span><span class='label'>user_id:</span> <span class='int'>1</span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_select'>select</span><span class='lparen'>(</span><span class='symbol'>:group_id</span><span class='rparen'>)</span>
264
+ <span class='rparen'>)</span>
265
+
266
+ <span class='comment'># In this example, we&#39;re not specifying any select for the relation we pass to
267
+ </span><span class='comment'># the query class. This is fine because the query automatically defaults to
268
+ </span><span class='comment'># selecting `id` if exactly one field is required (`fields: 1`) and no select is
269
+ </span><span class='comment'># specifyed. You can control this further with the option `default_select`.
270
+ </span><span class='const'>FetchUsersInGroup</span><span class='period'>.</span><span class='id identifier rubyid_run'>run</span><span class='lparen'>(</span><span class='const'>Group</span><span class='period'>.</span><span class='id identifier rubyid_where'>where</span><span class='lparen'>(</span><span class='label'>color:</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>red</span><span class='tstring_end'>&#39;</span></span><span class='rparen'>)</span><span class='rparen'>)</span>
271
+ </code></pre>
272
+
273
+ <h2 id="label-Parameters">Parameters</h2>
274
+
275
+ <p>Both query classes can be parameterized using a hash called
276
+ <code>params</code>. It is recommended to specify and validate input
277
+ parameters in every query. For this purpose, Inquery provides the
278
+ <code>schema</code> method witch integrates the <a
279
+ href="https://github.com/sitrox/schemacop">Schemacop</a> validation Gem:</p>
280
+
281
+ <pre class="code ruby"><code class="ruby"><span class='kw'>class</span> <span class='const'>SomeQueryClass</span> <span class='op'>&lt;</span> <span class='const'>Inquery</span><span class='op'>::</span><span class='const'>Query</span>
282
+ <span class='id identifier rubyid_schema'>schema</span><span class='lparen'>(</span>
283
+ <span class='label'>some_param:</span> <span class='symbol'>:integer</span><span class='comma'>,</span>
284
+ <span class='label'>some_other_param:</span> <span class='lbrace'>{</span>
285
+ <span class='label'>hash:</span> <span class='lbrace'>{</span>
286
+ <span class='label'>some_field:</span> <span class='symbol'>:string</span>
287
+ <span class='rbrace'>}</span>
288
+ <span class='rbrace'>}</span>
289
+ <span class='rparen'>)</span>
290
+
291
+ <span class='comment'># ...
292
+ </span><span class='kw'>end</span>
293
+ </code></pre>
294
+
295
+ <p>The schema is validated at query class instantiation. An exception will be
296
+ raised if the given params do not match the schema specified. See
297
+ documentation of the Schemacop Gem for more information on how to specify
298
+ schemas.</p>
299
+
300
+ <p>Parameters can be accessed using either <code>params</code> or
301
+ <code>osparams</code>. The method <code>osparams</code> automatically wraps
302
+ <code>params</code> in an <code>OpenStruct</code> for more convenient
303
+ access.</p>
304
+
305
+ <pre class="code ruby"><code class="ruby"><span class='kw'>class</span> <span class='const'>SomeQueryClass</span> <span class='op'>&lt;</span> <span class='const'>Inquery</span><span class='op'>::</span><span class='const'>Query</span>
306
+ <span class='kw'>def</span> <span class='id identifier rubyid_run'>run</span>
307
+ <span class='const'>User</span><span class='period'>.</span><span class='id identifier rubyid_where'>where</span><span class='lparen'>(</span>
308
+ <span class='label'>active:</span> <span class='id identifier rubyid_params'>params</span><span class='lbracket'>[</span><span class='symbol'>:active</span><span class='rbracket'>]</span><span class='comma'>,</span>
309
+ <span class='label'>username:</span> <span class='id identifier rubyid_osparams'>osparams</span><span class='period'>.</span><span class='id identifier rubyid_search'>search</span>
310
+ <span class='rparen'>)</span>
311
+ <span class='kw'>end</span>
312
+ <span class='kw'>end</span>
313
+ </code></pre>
314
+
315
+ <h2 id="label-Rails+integration">Rails integration</h2>
316
+
317
+ <p>While it is optional, Inquery has been written from the ground up to be
318
+ perfectly integrated into any Rails application. It has proven to be a
319
+ winning concept to extract all complex queries into separate classes that
320
+ are independently executable and testable.</p>
321
+
322
+ <h3 id="label-Directory+structure">Directory structure</h3>
323
+
324
+ <p>While not enforced, it is encouraged to use the following structure for
325
+ storing your query classes:</p>
326
+ <ul><li>
327
+ <p>All domain-specific query classes reside in <code>app/queries</code>.</p>
328
+ </li><li>
329
+ <p>They&#39;re in the module <code>Queries</code>.</p>
330
+ </li><li>
331
+ <p>Queries are further grouped by the model they return (and not the model
332
+ they receive). For instance, a class fetching all active users could be
333
+ located at <code>Queries::User::FetchActive</code> and would reside under
334
+ <code>app/queries/user/fetch_active.rb</code>.</p>
335
+ </li></ul>
336
+
337
+ <p>There are some key benefits to this approach:</p>
338
+ <ul><li>
339
+ <p>As it should, domain-specific code is located within <code>app/</code>.</p>
340
+ </li><li>
341
+ <p>As queries are grouped by the model they return and consistently named,
342
+ they&#39;re easy to locate and it does not take much thought where to put
343
+ and how to name new query classes.</p>
344
+ </li><li>
345
+ <p>As there is a single file per query class, it&#39;s a breeze to list all
346
+ queries, i.e. to check their naming for consistency.</p>
347
+ </li><li>
348
+ <p>If you&#39;re using the same layout for your unit tests, it is absolutely
349
+ clear where to find the corresponding unit tests for each one of your
350
+ query classes.</p>
351
+ </li></ul>
352
+
353
+ <h2 id="label-Copyright">Copyright</h2>
354
+
355
+ <p>Copyright © 2016 Sitrox. See <code>LICENSE</code> for further details.</p>
356
+ </div></div>
357
+
358
+ <div id="footer">
359
+ Generated on Thu Jun 9 10:26:30 2016 by
360
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
361
+ 0.8.7.6 (ruby-2.2.3).
362
+ </div>
363
+
364
+ </body>
365
+ </html>
@@ -0,0 +1,219 @@
1
+ function createSourceLinks() {
2
+ $('.method_details_list .source_code').
3
+ before("<span class='showSource'>[<a href='#' class='toggleSource'>View source</a>]</span>");
4
+ $('.toggleSource').toggle(function() {
5
+ $(this).parent().nextAll('.source_code').slideDown(100);
6
+ $(this).text("Hide source");
7
+ },
8
+ function() {
9
+ $(this).parent().nextAll('.source_code').slideUp(100);
10
+ $(this).text("View source");
11
+ });
12
+ }
13
+
14
+ function createDefineLinks() {
15
+ var tHeight = 0;
16
+ $('.defines').after(" <a href='#' class='toggleDefines'>more...</a>");
17
+ $('.toggleDefines').toggle(function() {
18
+ tHeight = $(this).parent().prev().height();
19
+ $(this).prev().show();
20
+ $(this).parent().prev().height($(this).parent().height());
21
+ $(this).text("(less)");
22
+ },
23
+ function() {
24
+ $(this).prev().hide();
25
+ $(this).parent().prev().height(tHeight);
26
+ $(this).text("more...");
27
+ });
28
+ }
29
+
30
+ function createFullTreeLinks() {
31
+ var tHeight = 0;
32
+ $('.inheritanceTree').toggle(function() {
33
+ tHeight = $(this).parent().prev().height();
34
+ $(this).parent().toggleClass('showAll');
35
+ $(this).text("(hide)");
36
+ $(this).parent().prev().height($(this).parent().height());
37
+ },
38
+ function() {
39
+ $(this).parent().toggleClass('showAll');
40
+ $(this).parent().prev().height(tHeight);
41
+ $(this).text("show all");
42
+ });
43
+ }
44
+
45
+ function fixBoxInfoHeights() {
46
+ $('dl.box dd.r1, dl.box dd.r2').each(function() {
47
+ $(this).prev().height($(this).height());
48
+ });
49
+ }
50
+
51
+ function searchFrameLinks() {
52
+ $('.full_list_link').click(function() {
53
+ toggleSearchFrame(this, $(this).attr('href'));
54
+ return false;
55
+ });
56
+ }
57
+
58
+ function toggleSearchFrame(id, link) {
59
+ var frame = $('#search_frame');
60
+ $('#search a').removeClass('active').addClass('inactive');
61
+ if (frame.attr('src') == link && frame.css('display') != "none") {
62
+ frame.slideUp(100);
63
+ $('#search a').removeClass('active inactive');
64
+ }
65
+ else {
66
+ $(id).addClass('active').removeClass('inactive');
67
+ frame.attr('src', link).slideDown(100);
68
+ }
69
+ }
70
+
71
+ function linkSummaries() {
72
+ $('.summary_signature').click(function() {
73
+ document.location = $(this).find('a').attr('href');
74
+ });
75
+ }
76
+
77
+ function framesInit() {
78
+ if (hasFrames) {
79
+ document.body.className = 'frames';
80
+ $('#menu .noframes a').attr('href', document.location);
81
+ try {
82
+ window.top.document.title = $('html head title').text();
83
+ } catch(error) {
84
+ // some browsers will not allow this when serving from file://
85
+ // but we don't want to stop the world.
86
+ }
87
+ }
88
+ else {
89
+ $('#menu .noframes a').text('frames').attr('href', framesUrl);
90
+ }
91
+ }
92
+
93
+ function keyboardShortcuts() {
94
+ if (window.top.frames.main) return;
95
+ $(document).keypress(function(evt) {
96
+ if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) return;
97
+ if (typeof evt.target !== "undefined" &&
98
+ (evt.target.nodeName == "INPUT" ||
99
+ evt.target.nodeName == "TEXTAREA")) return;
100
+ switch (evt.charCode) {
101
+ case 67: case 99: $('#class_list_link').click(); break; // 'c'
102
+ case 77: case 109: $('#method_list_link').click(); break; // 'm'
103
+ case 70: case 102: $('#file_list_link').click(); break; // 'f'
104
+ default: break;
105
+ }
106
+ });
107
+ }
108
+
109
+ function summaryToggle() {
110
+ $('.summary_toggle').click(function() {
111
+ if (localStorage) {
112
+ localStorage.summaryCollapsed = $(this).text();
113
+ }
114
+ $('.summary_toggle').each(function() {
115
+ $(this).text($(this).text() == "collapse" ? "expand" : "collapse");
116
+ var next = $(this).parent().parent().nextAll('ul.summary').first();
117
+ if (next.hasClass('compact')) {
118
+ next.toggle();
119
+ next.nextAll('ul.summary').first().toggle();
120
+ }
121
+ else if (next.hasClass('summary')) {
122
+ var list = $('<ul class="summary compact" />');
123
+ list.html(next.html());
124
+ list.find('.summary_desc, .note').remove();
125
+ list.find('a').each(function() {
126
+ $(this).html($(this).find('strong').html());
127
+ $(this).parent().html($(this)[0].outerHTML);
128
+ });
129
+ next.before(list);
130
+ next.toggle();
131
+ }
132
+ });
133
+ return false;
134
+ });
135
+ if (localStorage) {
136
+ if (localStorage.summaryCollapsed == "collapse") {
137
+ $('.summary_toggle').first().click();
138
+ }
139
+ else localStorage.summaryCollapsed = "expand";
140
+ }
141
+ }
142
+
143
+ function fixOutsideWorldLinks() {
144
+ $('a').each(function() {
145
+ if (window.location.host != this.host) this.target = '_parent';
146
+ });
147
+ }
148
+
149
+ function generateTOC() {
150
+ if ($('#filecontents').length === 0) return;
151
+ var _toc = $('<ol class="top"></ol>');
152
+ var show = false;
153
+ var toc = _toc;
154
+ var counter = 0;
155
+ var tags = ['h2', 'h3', 'h4', 'h5', 'h6'];
156
+ var i;
157
+ if ($('#filecontents h1').length > 1) tags.unshift('h1');
158
+ for (i = 0; i < tags.length; i++) { tags[i] = '#filecontents ' + tags[i]; }
159
+ var lastTag = parseInt(tags[0][1], 10);
160
+ $(tags.join(', ')).each(function() {
161
+ if ($(this).parents('.method_details .docstring').length != 0) return;
162
+ if (this.id == "filecontents") return;
163
+ show = true;
164
+ var thisTag = parseInt(this.tagName[1], 10);
165
+ if (this.id.length === 0) {
166
+ var proposedId = $(this).attr('toc-id');
167
+ if (typeof(proposedId) != "undefined") this.id = proposedId;
168
+ else {
169
+ var proposedId = $(this).text().replace(/[^a-z0-9-]/ig, '_');
170
+ if ($('#' + proposedId).length > 0) { proposedId += counter; counter++; }
171
+ this.id = proposedId;
172
+ }
173
+ }
174
+ if (thisTag > lastTag) {
175
+ for (i = 0; i < thisTag - lastTag; i++) {
176
+ var tmp = $('<ol/>'); toc.append(tmp); toc = tmp;
177
+ }
178
+ }
179
+ if (thisTag < lastTag) {
180
+ for (i = 0; i < lastTag - thisTag; i++) toc = toc.parent();
181
+ }
182
+ var title = $(this).attr('toc-title');
183
+ if (typeof(title) == "undefined") title = $(this).text();
184
+ toc.append('<li><a href="#' + this.id + '">' + title + '</a></li>');
185
+ lastTag = thisTag;
186
+ });
187
+ if (!show) return;
188
+ html = '<div id="toc"><p class="title"><a class="hide_toc" href="#"><strong>Table of Contents</strong></a> <small>(<a href="#" class="float_toc">left</a>)</small></p></div>';
189
+ $('#content').prepend(html);
190
+ $('#toc').append(_toc);
191
+ $('#toc .hide_toc').toggle(function() {
192
+ $('#toc .top').slideUp('fast');
193
+ $('#toc').toggleClass('hidden');
194
+ $('#toc .title small').toggle();
195
+ }, function() {
196
+ $('#toc .top').slideDown('fast');
197
+ $('#toc').toggleClass('hidden');
198
+ $('#toc .title small').toggle();
199
+ });
200
+ $('#toc .float_toc').toggle(function() {
201
+ $(this).text('float');
202
+ $('#toc').toggleClass('nofloat');
203
+ }, function() {
204
+ $(this).text('left');
205
+ $('#toc').toggleClass('nofloat');
206
+ });
207
+ }
208
+
209
+ $(framesInit);
210
+ $(createSourceLinks);
211
+ $(createDefineLinks);
212
+ $(createFullTreeLinks);
213
+ $(fixBoxInfoHeights);
214
+ $(searchFrameLinks);
215
+ $(linkSummaries);
216
+ $(keyboardShortcuts);
217
+ $(summaryToggle);
218
+ $(fixOutsideWorldLinks);
219
+ $(generateTOC);