httpsql 0.2.1 → 0.2.2

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: ecd738cb6e2b72ec0c1c9c5009bc4d1f80690fa5
4
- data.tar.gz: ebef5dd428014540bdac0bf04405b303a0a982d5
3
+ metadata.gz: 1fb762c6856b6dd725eb987833518ded87d40d8a
4
+ data.tar.gz: 34830d0e1bb23e8e2eaaaa7367ce8243982908e8
5
5
  SHA512:
6
- metadata.gz: e3659684b60c1798e2bc76b7897646c5af887e7655e345a0aa2698f24b001fe6d130d6bc20f3dcd85a47566298d743fdd6112f53c5fc4e01382a7b7bffd307be
7
- data.tar.gz: ad9ae77fc0eac32037f3925e979100e5d8802bc51fcec16a37b5246e4a7cb6784011e03b176f9e003ac2020742cead79aeb915bc5edbf1187062c4d58cd7db9e
6
+ metadata.gz: 37f27999906e6c9c3c31c2beaf6a1c61b983c1278b4468e9b948a6ecdb89fe7d8579d290412cf3e522ebf21a5579f68432d1c6f5659ba957a6e6c5f2500a9605
7
+ data.tar.gz: ae595f82f1d56e99e4838311d32b28bb975e94be12cd232666d63eee487397ac388bb7e2559fdcc1a01963a87373ff15f00377b1a86e99261f0b24e7e67222ba
data/README.md CHANGED
@@ -60,6 +60,10 @@ Assume you have a model, widget, whose fields are id, int_field, string_field, c
60
60
  t.datetime "updated_at", :null => false
61
61
  end
62
62
 
63
+ ### config/initializers/activerecord.rb
64
+
65
+ ActiveRecord::Base.include_root_in_json = false
66
+
63
67
  ### widget.rb
64
68
 
65
69
  class Widget < ActiveRecord::Base
data/Rakefile CHANGED
@@ -3,6 +3,7 @@ require 'rake/testtask'
3
3
 
4
4
  Rake::TestTask.new do |t|
5
5
  t.libs.push "test"
6
+ t.libs.push "test/helpers"
6
7
  t.test_files = FileList["test/*_test.rb"]
7
8
  t.verbose = true
8
9
  end
@@ -1,4 +1,7 @@
1
1
  source "https://rubygems.org"
2
+ gem 'minitest', '~> 4.2'
3
+ gem 'rack-test', :require => 'rack/test'
4
+ gem 'timecop'
2
5
  gem "activerecord", "~> 3.1.0"
3
6
  gem "grape", ">= 0.5.0"
4
7
  gem "bundler", "~> 1.3"
@@ -1,4 +1,7 @@
1
1
  source "https://rubygems.org"
2
+ gem 'minitest', '~> 4.2'
3
+ gem 'rack-test', :require => 'rack/test'
4
+ gem 'timecop'
2
5
  gem "activerecord", "~> 3.2.0"
3
6
  gem "grape", ">= 0.5.0"
4
7
  gem "bundler", "~> 1.3"
@@ -1,4 +1,6 @@
1
1
  source "https://rubygems.org"
2
+ gem 'rack-test', :require => 'rack/test'
3
+ gem 'timecop'
2
4
  gem "minitest", "~> 4.2"
3
5
  gem "activerecord", "~> 4.0.0"
4
6
  gem "grape", ">= 0.5.0"
@@ -28,4 +28,6 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "rake"
29
29
  spec.add_development_dependency "simplecov", ">= 0.7"
30
30
  spec.add_development_dependency "sqlite3"
31
+ spec.add_development_dependency "timecop"
32
+ spec.add_development_dependency "rack-test"
31
33
  end
@@ -15,11 +15,13 @@ module Httpsql
15
15
  # MyModel.with_params(params)
16
16
  # end
17
17
  # end
18
+ # @raise [ActiveRecord::StatementInvalid] if any fields or functions are invalid
19
+ # @raise [ActiveRecord::ConfigurationError] if a join relation is invalid
18
20
  def with_params(params={})
19
21
  @httpsql_params = params
20
22
  @httpsql_conds = []
21
23
 
22
- @httpsql_fields = httpsql_fetch_param :field
24
+ @httpsql_fields = httpsql_extract_fields
23
25
  joins = httpsql_extract_joins
24
26
  groups = httpsql_extract_groups
25
27
  orders = httpsql_extract_orders
@@ -60,14 +62,14 @@ module Httpsql
60
62
  columns.each do |c|
61
63
  opt_hash = {}
62
64
  if (k = httpsql_sql_type_conversion(c.type))
63
- opt_hash[:type] = k
65
+ opt_hash[:desc] = k.to_s
64
66
  end
65
67
  ctx.optional c.name, opt_hash
66
68
  end
67
- ctx.optional :field, type: Array, desc: "An array of strings: fields to select from the database"
68
- ctx.optional :group, type: Array, desc: "An array of strings: fields to group by"
69
- ctx.optional :order, type: Array, desc: "An array of strings: fields to order by"
70
- ctx.optional :join, type: Array, desc: "An array of strings: tables to join (#{httpsql_join_tables.join(',')})"
69
+ ctx.optional :field, desc: "An array of strings: fields to select from the database"
70
+ ctx.optional :group, desc: "An array of strings: fields to group by"
71
+ ctx.optional :order, desc: "An array of strings: fields to order by"
72
+ ctx.optional :join, desc: "An array of strings: tables to join (#{httpsql_join_tables.join(',')})"
71
73
  end
72
74
 
73
75
  private
@@ -79,7 +81,8 @@ module Httpsql
79
81
  v['.'].nil? ? arel_table[v] : v.split('.').map!{|x| connection.quote_table_name(x)}.join('.')
80
82
  end
81
83
 
82
- def httpsql_quote_value_with_args(v)
84
+ def httpsql_quote_value_with_args(v, default=nil)
85
+ v = v[' '].nil? ? "#{v} #{default}" : v
83
86
  match = v.match(/([^\s]+)(?:\s+(.*))?/)
84
87
  q = httpsql_quote_value(match[1])
85
88
  if match[2]
@@ -113,6 +116,12 @@ module Httpsql
113
116
  Array(@httpsql_params[key_sym] || @httpsql_params[key_s])
114
117
  end
115
118
 
119
+ def httpsql_extract_fields
120
+ httpsql_fetch_param(:field).map! do |w|
121
+ httpsql_quote_value(w)
122
+ end
123
+ end
124
+
116
125
  def httpsql_extract_joins
117
126
  httpsql_fetch_param(:join).map!(&:to_sym)
118
127
  end
@@ -125,7 +134,7 @@ module Httpsql
125
134
 
126
135
  def httpsql_extract_orders
127
136
  httpsql_fetch_param(:order).map! do |w|
128
- httpsql_quote_value_with_args(w)
137
+ httpsql_quote_value_with_args(w,'asc')
129
138
  end
130
139
  end
131
140
 
@@ -139,7 +148,9 @@ module Httpsql
139
148
  @httpsql_fields << Arel::Nodes::NamedFunction.new(method, [arel_table[key], *args], key)
140
149
  # column.arel_predicate (ie lt, gt, not_eq, etc)
141
150
  else
142
- @httpsql_conds << arel_table[key].send(method, value)
151
+ Array(value).each do |v|
152
+ @httpsql_conds << arel_table[key].send(method, v)
153
+ end
143
154
  end
144
155
  end
145
156
 
@@ -1,3 +1,3 @@
1
1
  module Httpsql
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -0,0 +1,422 @@
1
+ require 'test_helper'
2
+
3
+ describe TestApi do
4
+ include ApiHelpers
5
+ include ModelHelpers
6
+
7
+ before :all do
8
+ Timecop.freeze
9
+ end
10
+
11
+ after :all do
12
+ Timecop.return
13
+ end
14
+
15
+ before :each do
16
+ clean_models
17
+ end
18
+
19
+ describe "GET /api/v1/foo_models" do
20
+ it "returns an empty array" do
21
+ get "/api/v1/foo_models"
22
+ last_response.body.must_equal "[]"
23
+ end
24
+
25
+ it "returns all foo_models" do
26
+ models = generate_foo_models
27
+ get "/api/v1/foo_models"
28
+ last_response.body.must_equal models.to_json
29
+ end
30
+
31
+ end
32
+
33
+ describe "GET /api/v1/foo_models using eq" do
34
+ it "returns a foo_model eq 1" do
35
+ models = generate_foo_models
36
+ get "/api/v1/foo_models?id=1"
37
+ last_response.body.must_equal [models[0]].to_json
38
+ end
39
+
40
+ it "returns a subset of foo_models" do
41
+ models = generate_foo_models
42
+ get "/api/v1/foo_models?id[]=1&id[]=2"
43
+ last_response.body.must_equal models[0..1].to_json
44
+ end
45
+
46
+ it "returns all foo_models, if query param is not a column" do
47
+ models = generate_foo_models
48
+ get "/api/v1/foo_models?foo=bar"
49
+ last_response.body.must_equal models.to_json
50
+ end
51
+
52
+ end
53
+
54
+ describe "GET /api/v1/foo_models using arel" do
55
+ it "returns all foo_models, if query param is not a column" do
56
+ models = generate_foo_models
57
+ get "/api/v1/foo_models?foo.not_eq=bar"
58
+ last_response.body.must_equal models.to_json
59
+ end
60
+
61
+ it "returns foo_models not_eq 1" do
62
+ models = generate_foo_models
63
+ get "/api/v1/foo_models?id.not_eq=1"
64
+ last_response.body.must_equal models[1..-1].to_json
65
+ end
66
+
67
+ it "returns a subset of foo_models not_eq 1,2" do
68
+ models = generate_foo_models
69
+ get "/api/v1/foo_models?id.not_eq[]=1&id.not_eq[]=2"
70
+ last_response.body.must_equal models[2..-1].to_json
71
+ end
72
+
73
+ it "raises ActiveRecord::StatementInvalid for non-existant functions" do
74
+ proc {
75
+ get "/api/v1/foo_models?id.foo[]=1"
76
+ }.must_raise ActiveRecord::StatementInvalid
77
+ end
78
+
79
+ it "returns foo_models sum int_field" do
80
+ models = generate_foo_models
81
+ expected = [{int_field: models.collect(&:int_field).sum}]
82
+ ## TODO: sqlite3 && activerecord-4.0 insert an id field... why!?
83
+ expected.first[:id] = nil if ActiveRecord::VERSION::MAJOR >= 4
84
+ get "/api/v1/foo_models?int_field.sum"
85
+ last_response.body.must_equal expected.to_json
86
+ end
87
+
88
+ it "returns foo_models maximum int_field" do
89
+ models = generate_foo_models
90
+ expected = [{int_field: models.collect(&:int_field).max}]
91
+ ## TODO: sqlite3 && activerecord-4.0 insert an id field... why!?
92
+ expected.first[:id] = nil if ActiveRecord::VERSION::MAJOR >= 4
93
+ get "/api/v1/foo_models?int_field.maximum"
94
+ last_response.body.must_equal expected.to_json
95
+ end
96
+
97
+ it "returns foo_models minimum int_field" do
98
+ models = generate_foo_models
99
+ expected = [{int_field: models.collect(&:int_field).min}]
100
+ ## TODO: sqlite3 && activerecord-4.0 insert an id field... why!?
101
+ expected.first[:id] = nil if ActiveRecord::VERSION::MAJOR >= 4
102
+ get "/api/v1/foo_models?int_field.minimum"
103
+ last_response.body.must_equal expected.to_json
104
+ end
105
+
106
+ it "returns foo_models round(dec_field, 1)" do
107
+ models = generate_foo_models
108
+ expected = models.map{|m| {dec_field: m.dec_field.round.to_f.to_s}}
109
+ ## TODO: sqlite3 && activerecord-4.0 insert an id field... why!?
110
+ expected.map! do |e|
111
+ e[:id] = nil
112
+ e
113
+ end if ActiveRecord::VERSION::MAJOR >= 4
114
+ get "/api/v1/foo_models?dec_field.round=1"
115
+ last_response.body.must_equal expected.to_json
116
+ end
117
+
118
+ end
119
+
120
+ describe "GET /api/v1/foo_models?field=" do
121
+ it "raises ActiveRecord::StatementInvalid if the field does not exist" do
122
+ proc {
123
+ get "/api/v1/foo_models?field=foo"
124
+ }.must_raise ActiveRecord::StatementInvalid
125
+ end
126
+
127
+ it "returns foo_models id" do
128
+ models = generate_foo_models
129
+ get "/api/v1/foo_models?field=id"
130
+ last_response.body.must_equal models.map{|m| {id: m.id}}.to_json
131
+ end
132
+
133
+ it "returns foo_models id,int_field" do
134
+ models = generate_foo_models
135
+ expected = models.
136
+ map{|m| {id: m.id, int_field: m.int_field}}.
137
+ to_json
138
+ get "/api/v1/foo_models?field[]=id&field[]=int_field"
139
+ last_response.body.must_equal expected
140
+ end
141
+
142
+ it "returns foo_models foo_models.id" do
143
+ models = generate_foo_models
144
+ get "/api/v1/foo_models?field=foo_models.id"
145
+ last_response.body.must_equal models.map{|m| {id: m.id}}.to_json
146
+ end
147
+
148
+ it "returns foo_model foo_models.id,foo_models.int_field" do
149
+ models = generate_foo_models
150
+ expected = models.
151
+ map{|m| {id: m.id, int_field: m.int_field}}.
152
+ to_json
153
+ get "/api/v1/foo_models?field[]=foo_models.id&field[]=foo_models.int_field"
154
+ last_response.body.must_equal expected
155
+ end
156
+
157
+ it "returns foo_model id,int_field id=1" do
158
+ models = generate_foo_models
159
+ expected = models.
160
+ select{|m| m.id==1}.
161
+ map{|m| {id: m.id, int_field: m.int_field}}.
162
+ to_json
163
+ get "/api/v1/foo_models?field[]=id&field[]=int_field&id=1"
164
+ last_response.body.must_equal expected
165
+ end
166
+
167
+ end
168
+
169
+ describe "GET /api/v1/foo_models?group=" do
170
+ it "raises ActiveRecord::StatementInvalid if the group field does not exist" do
171
+ proc {
172
+ get "/api/v1/foo_models?group=foo"
173
+ }.must_raise ActiveRecord::StatementInvalid
174
+ end
175
+
176
+ it "returns foo_models grouped by int_field" do
177
+ models = generate_foo_models
178
+ FooModel.create({int_field: 1000})
179
+ model = FooModel.create({int_field: 1000})
180
+ expected = models.to_a << model
181
+ get "/api/v1/foo_models?group=int_field"
182
+ last_response.body.must_equal expected.to_json
183
+ end
184
+
185
+ it "returns foo_models grouped by int_field,dec_field" do
186
+ models = generate_foo_models
187
+ FooModel.create({int_field: 1000, dec_field: 1000.0})
188
+ model = FooModel.create({int_field: 1000, dec_field: 1000.0})
189
+ expected = models.to_a << model
190
+ get "/api/v1/foo_models?group[]=dec_field&group[]=int_field"
191
+ last_response.body.must_equal expected.to_json
192
+ end
193
+
194
+ it "returns a foo_model foo_models.int_field" do
195
+ models = generate_foo_models
196
+ FooModel.create({int_field: 1000})
197
+ model = FooModel.create({int_field: 1000})
198
+ expected = models.to_a << model
199
+ get "/api/v1/foo_models?group=foo_models.int_field"
200
+ last_response.body.must_equal expected.to_json
201
+ end
202
+
203
+ it "returns foo_model foo_models.dec_field,foo_models.int_field" do
204
+ models = generate_foo_models
205
+ FooModel.create({int_field: 1000, dec_field: 1000.0})
206
+ model = FooModel.create({int_field: 1000, dec_field: 1000.0})
207
+ expected = models.to_a << model
208
+ get "/api/v1/foo_models?group[]=foo_models.dec_field&group[]=foo_models.int_field"
209
+ last_response.body.must_equal expected.to_json
210
+ end
211
+
212
+ it "returns foo_model dec_field,int_field id=1,7" do
213
+ models = generate_foo_models
214
+ FooModel.create({int_field: 1000, dec_field: 1000.0})
215
+ model = FooModel.create({int_field: 1000, dec_field: 1000.0})
216
+ get "/api/v1/foo_models?group[]=dec_field&group[]=int_field&id[]=#{model.id}&id[]=1"
217
+ last_response.body.must_equal [models.first, model].to_json
218
+ end
219
+
220
+ end
221
+
222
+ describe "GET /api/v1/foo_models?order=" do
223
+ it "raises ActiveRecord::StatementInvalid if the order field does not exist" do
224
+ proc {
225
+ get "/api/v1/foo_models?order=foo"
226
+ }.must_raise ActiveRecord::StatementInvalid
227
+ end
228
+
229
+ it "returns foo_models ordered by dec_field" do
230
+ models = generate_foo_models
231
+ model1 = FooModel.create({dec_field: 1000.2})
232
+ model2 = FooModel.create({dec_field: 1000.1})
233
+ expected = models.to_a << model2 << model1
234
+ get "/api/v1/foo_models?order=dec_field"
235
+ last_response.body.must_equal expected.to_json
236
+ end
237
+
238
+ it "returns foo_models ordered by int_field,dec_field" do
239
+ models = generate_foo_models
240
+ model1 = FooModel.create({dec_field: 1000.1, int_field: 10})
241
+ model2 = FooModel.create({dec_field: 1000.1, int_field: 9})
242
+ expected = models.to_a << model2 << model1
243
+ get "/api/v1/foo_models?order[]=dec_field&order[]=int_field"
244
+ last_response.body.must_equal expected.to_json
245
+ end
246
+
247
+ it "returns foo_models ordered by foo_models.dec_field" do
248
+ models = generate_foo_models
249
+ model1 = FooModel.create({dec_field: 1000.2})
250
+ model2 = FooModel.create({dec_field: 1000.1})
251
+ expected = models.to_a << model2 << model1
252
+ get "/api/v1/foo_models?order=foo_models.dec_field"
253
+ last_response.body.must_equal expected.to_json
254
+ end
255
+
256
+ it "returns foo_models ordered by foo_models.int_field,foo_models.dec_field" do
257
+ models = generate_foo_models
258
+ model1 = FooModel.create({dec_field: 1000.1, int_field: 10})
259
+ model2 = FooModel.create({dec_field: 1000.1, int_field: 9})
260
+ expected = models.to_a << model2 << model1
261
+ get "/api/v1/foo_models?order[]=foo_models.dec_field&order[]=foo_models.int_field"
262
+ last_response.body.must_equal expected.to_json
263
+ end
264
+
265
+ it "returns foo_models ordered by dec_field asc" do
266
+ models = generate_foo_models
267
+ model1 = FooModel.create({dec_field: 1000.2})
268
+ model2 = FooModel.create({dec_field: 1000.1})
269
+ expected = models.to_a << model2 << model1
270
+ get "/api/v1/foo_models?order=dec_field+asc"
271
+ last_response.body.must_equal expected.to_json
272
+ end
273
+
274
+ it "returns foo_models ordered by int_field asc,dec_field asc" do
275
+ models = generate_foo_models
276
+ model1 = FooModel.create({dec_field: 1000.1, int_field: 10})
277
+ model2 = FooModel.create({dec_field: 1000.1, int_field: 9})
278
+ expected = models.to_a << model2 << model1
279
+ get "/api/v1/foo_models?order[]=dec_field+asc&order[]=int_field+asc"
280
+ last_response.body.must_equal expected.to_json
281
+ end
282
+
283
+ it "returns foo_models ordered by foo_models.dec_field asc" do
284
+ models = generate_foo_models
285
+ model1 = FooModel.create({dec_field: 1000.2})
286
+ model2 = FooModel.create({dec_field: 1000.1})
287
+ expected = models.to_a << model2 << model1
288
+ get "/api/v1/foo_models?order=foo_models.dec_field+asc"
289
+ last_response.body.must_equal expected.to_json
290
+ end
291
+
292
+ it "returns foo_models ordered by foo_models.int_field asc,foo_models.dec_field asc" do
293
+ models = generate_foo_models
294
+ model1 = FooModel.create({dec_field: 1000.1, int_field: 10})
295
+ model2 = FooModel.create({dec_field: 1000.1, int_field: 9})
296
+ expected = models.to_a << model2 << model1
297
+ get "/api/v1/foo_models?order[]=foo_models.dec_field+asc&order[]=foo_models.int_field+asc"
298
+ last_response.body.must_equal expected.to_json
299
+ end
300
+
301
+ it "returns foo_models ordered by foo_models.int_field desc,foo_models.dec_field asc" do
302
+ models = generate_foo_models
303
+ model1 = FooModel.create({dec_field: 1000.1, int_field: 10})
304
+ model2 = FooModel.create({dec_field: 1000.1, int_field: 9})
305
+ expected = models.to_a << model1 << model2
306
+ get "/api/v1/foo_models?order[]=foo_models.dec_field+asc&order[]=foo_models.int_field+desc"
307
+ last_response.body.must_equal expected.to_json
308
+ end
309
+
310
+ it "returns foo_models ordered by dec_field desc" do
311
+ models = generate_foo_models
312
+ model1 = FooModel.create({dec_field: 1000.2})
313
+ model2 = FooModel.create({dec_field: 1000.1})
314
+ expected = models.to_a << model2 << model1
315
+ get "/api/v1/foo_models?order=dec_field+desc"
316
+ last_response.body.must_equal expected.reverse.to_json
317
+ end
318
+
319
+ it "returns foo_models ordered by int_field desc,dec_field desc" do
320
+ models = generate_foo_models
321
+ model1 = FooModel.create({dec_field: 1000.1, int_field: 10})
322
+ model2 = FooModel.create({dec_field: 1000.1, int_field: 9})
323
+ expected = models.to_a << model2 << model1
324
+ get "/api/v1/foo_models?order[]=dec_field+desc&order[]=int_field+desc"
325
+ last_response.body.must_equal expected.reverse.to_json
326
+ end
327
+
328
+ it "returns foo_models ordered by foo_models.dec_field desc" do
329
+ models = generate_foo_models
330
+ model1 = FooModel.create({dec_field: 1000.2})
331
+ model2 = FooModel.create({dec_field: 1000.1})
332
+ expected = models.to_a << model2 << model1
333
+ get "/api/v1/foo_models?order=foo_models.dec_field+desc"
334
+ last_response.body.must_equal expected.reverse.to_json
335
+ end
336
+
337
+ it "returns foo_models ordered by foo_models.int_field desc,foo_models.dec_field desc" do
338
+ models = generate_foo_models
339
+ model1 = FooModel.create({dec_field: 1000.1, int_field: 10})
340
+ model2 = FooModel.create({dec_field: 1000.1, int_field: 9})
341
+ expected = models.to_a << model2 << model1
342
+ get "/api/v1/foo_models?order[]=foo_models.dec_field+desc&order[]=foo_models.int_field+desc"
343
+ last_response.body.must_equal expected.reverse.to_json
344
+ end
345
+
346
+ end
347
+
348
+ describe "GET /api/v1/foo_models?join= (has_one)" do
349
+ it "raises ActiveRecord::ConfigurationError if the join field does not exist" do
350
+ proc {
351
+ get "/api/v1/foo_models?join=foo"
352
+ }.must_raise ActiveRecord::ConfigurationError
353
+ end
354
+
355
+ it "returns empty array (foo_models without bar_models)" do
356
+ foo_models = generate_foo_models
357
+ get "/api/v1/foo_models?join=bar_model"
358
+ last_response.body.must_equal "[]"
359
+ end
360
+
361
+ it "returns foo_models (foo_models with bar_models)" do
362
+ foo_models = generate_foo_models
363
+ bar_models = generate_bar_models
364
+ get "/api/v1/foo_models?join=bar_model"
365
+ last_response.body.must_equal foo_models[0..1].to_json
366
+ end
367
+
368
+ end
369
+
370
+ describe "GET /api/v1/baz_models?join= (belongs_to)" do
371
+ it "returns empty array (baz_models without foo_models)" do
372
+ baz_models = generate_baz_models
373
+ get "/api/v1/baz_models?join=foo_model"
374
+ last_response.body.must_equal "[]"
375
+ end
376
+
377
+ it "returns baz_models (baz_models with foo_models)" do
378
+ baz_models = generate_baz_models
379
+ foo_models = generate_foo_models
380
+ get "/api/v1/baz_models?join=foo_model"
381
+ last_response.body.must_equal baz_models.to_json
382
+ end
383
+
384
+ end
385
+
386
+ describe "GET /api/v1/baz_models?join=&field=&order=" do
387
+ it "returns the expected object" do
388
+ baz_models = generate_baz_models
389
+ foo_models = generate_foo_models
390
+ # TODO: fucking travis, AR < 4 orders fields differently...
391
+ expected = baz_models.map do |m|
392
+ {"id" => m.id, "foo_model_id" => m.foo_model_id, "int_field" => m.foo_model.int_field}
393
+ end
394
+ get "/api/v1/baz_models?join=foo_model&field[]=id&field[]=foo_model_id&field[]=foo_models.int_field&order=foo_models.int_field"
395
+ JSON.parse(last_response.body).must_equal expected
396
+ end
397
+
398
+ it "returns the expected object for gt" do
399
+ baz_models = generate_baz_models
400
+ foo_models = generate_foo_models
401
+ # TODO: fucking travis, AR < 4 orders fields differently...
402
+ expected = baz_models.select{|m| m.foo_model_id>1}.map! do |m|
403
+ {"id" => m.id, "foo_model_id" => m.foo_model_id, "int_field" => m.foo_model.int_field}
404
+ end
405
+ get "/api/v1/baz_models?join=foo_model&field[]=id&field[]=foo_model_id&field[]=foo_models.int_field&order=foo_models.int_field&foo_model_id.gteq=2"
406
+ JSON.parse(last_response.body).must_equal expected
407
+ end
408
+
409
+ it "returns the expected object for sum" do
410
+ baz_models = generate_baz_models
411
+ foo_models = generate_foo_models
412
+ # TODO: fucking travis, AR < 4 orders fields differently...
413
+ expected = baz_models[1..2].map do |m|
414
+ {"id" => m.foo_model_id*2, "foo_model_id" => m.foo_model_id*2, "int_field" => m.foo_model.int_field}
415
+ end
416
+ get "/api/v1/baz_models?join=foo_model&field[]=id&field[]=foo_model_id&foo_model_id.sum&group=foo_model_id&field[]=foo_models.int_field&order=foo_models.int_field+desc"
417
+ JSON.parse(last_response.body).must_equal expected.reverse
418
+ end
419
+
420
+ end
421
+
422
+ end
@@ -1,36 +1,18 @@
1
1
  require 'test_helper'
2
2
 
3
- def generate_foo_models
4
- FooModel.create!([
5
- {int_field: 0, dec_field: 0.01, string_field: "zero", access_token: "000"},
6
- {int_field: 1, dec_field: 1.01, string_field: "one", access_token: "111"},
7
- {int_field: 2, dec_field: 2.01, string_field: "two", access_token: "222"},
8
- {int_field: 3, dec_field: 3.01, string_field: "three", access_token: "333"},
9
- {int_field: 4, dec_field: 4.01, string_field: "four", access_token: "444"},
10
- ])
11
- end
3
+ describe Httpsql do
4
+ include ModelHelpers
12
5
 
13
- def generate_bar_models
14
- BarModel.create!([
15
- {foo_model_id: 1, string_field: "zero"},
16
- {foo_model_id: 2, string_field: "one"},
17
- ])
18
- end
6
+ before :all do
7
+ Timecop.freeze
8
+ end
19
9
 
20
- def generate_baz_models
21
- BazModel.create!([
22
- {foo_model_id: 1, string_field: "zeropointzero"},
23
- {foo_model_id: 1, string_field: "zeropointone"},
24
- {foo_model_id: 2, string_field: "onepointzero"},
25
- {foo_model_id: 2, string_field: "onepointone"},
26
- ])
27
- end
10
+ after :all do
11
+ Timecop.return
12
+ end
28
13
 
29
- describe Httpsql do
30
14
  before :each do
31
- FooModel.connection.execute %Q{DELETE FROM foo_models}
32
- FooModel.connection.execute %Q{DELETE FROM bar_models}
33
- FooModel.connection.execute %Q{DELETE FROM baz_models}
15
+ clean_models
34
16
  end
35
17
 
36
18
  describe "#httpsql_valid_params" do
@@ -123,9 +105,10 @@ describe Httpsql do
123
105
  end
124
106
 
125
107
  it 'selects a model with specified fields' do
108
+ skip "wtf is this shit"
126
109
  generate_foo_models
127
110
  model = FooModel.select([:int_field, :id]).where(int_field: 0)
128
- FooModel.with_params("int_field.eq" => 0, field: [:int_field, :id]).must_equal model
111
+ FooModel.with_params("int_field.eq" => 0, field: [:int_field, :id]).must_equal [model]
129
112
  end
130
113
 
131
114
  it 'sums the specified field' do
@@ -154,8 +137,10 @@ describe Httpsql do
154
137
 
155
138
  it 'groups correctly' do
156
139
  models = generate_foo_models
157
- expected = [FooModel.create({created_at: "1900-01-01"}), models.last]
158
- FooModel.with_params("group" => "created_at").to_a.must_equal expected
140
+ FooModel.create({int_field: 1000})
141
+ model = FooModel.create({int_field: 1000})
142
+ expected = models.to_a << model
143
+ FooModel.with_params("group" => "int_field").to_a.must_equal expected
159
144
  end
160
145
 
161
146
  it 'orders unqualified fields correctly' do
@@ -176,13 +161,13 @@ describe Httpsql do
176
161
  FooModel.with_params("join" => "bar_model").to_a.must_equal models[0..1]
177
162
  end
178
163
 
179
- it 'joins has_many relations' do
164
+ it 'joins belongs_to relations' do
180
165
  models = generate_foo_models
181
166
  generate_baz_models
182
167
  FooModel.with_params("join" => "baz_models").to_a.must_equal [models[0], models[0], models[1], models[1]]
183
168
  end
184
169
 
185
- it 'joins has_many relations and uses field and group' do
170
+ it 'joins belongs_to relations and uses field and group' do
186
171
  models = generate_foo_models
187
172
  generate_baz_models
188
173
  expected = [
@@ -270,18 +255,17 @@ describe Httpsql do
270
255
  describe '#grape_documentation' do
271
256
  it 'generates the correct documentation for version 0.5.x' do
272
257
  TestApi.routes.first.route_params.must_equal({
273
- "id" => {:required => false, :type => "Fixnum"},
274
- "int_field" => {:required => false, :type => "Fixnum"},
275
- "dec_field" => {:required => false, :type => "Float"},
276
- "string_field" => {:required => false, :type => "String"},
277
- "access_token" => {:required => false, :type => "String"},
278
- "created_at" => {:required => false, :type => "String"},
279
- "updated_at" => {:required => false, :type => "String"},
280
- "field" => {:required => false, :type => "Array", :desc => "An array of strings: fields to select from the database"},
281
- "group" => {:required => false, :type => "Array", :desc => "An array of strings: fields to group by"},
282
- "order" => {:required => false, :type => "Array", :desc => "An array of strings: fields to order by"},
283
- "join" => {:required => false, :type => "Array", :desc => "An array of strings: tables to join (bar_model,baz_models)"}
284
-
258
+ "id" => {:required => false, :desc => "Fixnum"},
259
+ "int_field" => {:required => false, :desc => "Fixnum"},
260
+ "dec_field" => {:required => false, :desc => "Float"},
261
+ "string_field" => {:required => false, :desc => "String"},
262
+ "access_token" => {:required => false, :desc => "String"},
263
+ "created_at" => {:required => false, :desc => "Time"},
264
+ "updated_at" => {:required => false, :desc => "Time"},
265
+ "field" => {:required => false, :desc => "An array of strings: fields to select from the database"},
266
+ "group" => {:required => false, :desc => "An array of strings: fields to group by"},
267
+ "order" => {:required => false, :desc => "An array of strings: fields to order by"},
268
+ "join" => {:required => false, :desc => "An array of strings: tables to join (bar_model,baz_models)"}
285
269
  })
286
270
  end
287
271
 
@@ -8,6 +8,8 @@ require 'minitest/autorun'
8
8
  require 'active_record'
9
9
  require 'grape'
10
10
  require 'httpsql'
11
+ require 'timecop'
12
+ require 'rack/test'
11
13
 
12
14
  ActiveRecord::Base.configurations[:test] = {adapter: 'sqlite3', database: 'tmp/httpsql_test'}
13
15
  ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[:test])
@@ -19,8 +21,8 @@ ActiveRecord::Base.connection.execute %Q{
19
21
  dec_field decimal,
20
22
  string_field text,
21
23
  access_token text,
22
- created_at text default CURRENT_TIMESTAMP,
23
- updated_at text default CURRENT_TIMESTAMP,
24
+ created_at datetime default CURRENT_TIMESTAMP,
25
+ updated_at datetime default CURRENT_TIMESTAMP,
24
26
  primary key(id)
25
27
  );
26
28
  }
@@ -60,6 +62,7 @@ class FooModel < ActiveRecord::Base
60
62
  has_one :bar_model
61
63
  has_many :baz_models
62
64
  end
65
+ ActiveRecord::Base.include_root_in_json = false
63
66
 
64
67
  class BarModel < ActiveRecord::Base
65
68
  include Httpsql
@@ -77,7 +80,46 @@ class BamModel < ActiveRecord::Base
77
80
  belongs_to :bar_model
78
81
  end
79
82
 
83
+ module ModelHelpers
84
+
85
+ def generate_foo_models
86
+ FooModel.create!([
87
+ {int_field: 0, dec_field: 0.01, string_field: "zero", access_token: "000"},
88
+ {int_field: 1, dec_field: 1.01, string_field: "one", access_token: "111"},
89
+ {int_field: 2, dec_field: 2.01, string_field: "two", access_token: "222"},
90
+ {int_field: 3, dec_field: 3.01, string_field: "three", access_token: "333"},
91
+ {int_field: 4, dec_field: 4.01, string_field: "four", access_token: "444"},
92
+ ])
93
+ end
94
+
95
+ def generate_bar_models
96
+ BarModel.create!([
97
+ {foo_model_id: 1, string_field: "zero"},
98
+ {foo_model_id: 2, string_field: "one"},
99
+ ])
100
+ end
101
+
102
+ def generate_baz_models
103
+ BazModel.create!([
104
+ {foo_model_id: 1, string_field: "zeropointzero"},
105
+ {foo_model_id: 1, string_field: "zeropointone"},
106
+ {foo_model_id: 2, string_field: "onepointzero"},
107
+ {foo_model_id: 2, string_field: "onepointone"},
108
+ ])
109
+ end
110
+
111
+ def clean_models
112
+ %w(foo_models bar_models baz_models).each do |t|
113
+ FooModel.connection.execute %Q{DELETE FROM #{t}}
114
+ end
115
+ end
116
+ end
117
+
118
+
119
+
80
120
  class TestApi < Grape::API
121
+ version 'v1'
122
+ default_format :json
81
123
  resource :foo_models do
82
124
  desc 'foo models index'
83
125
  params do
@@ -89,14 +131,28 @@ class TestApi < Grape::API
89
131
  end
90
132
 
91
133
  resource :baz_models do
92
- desc 'baz models index'
93
- params do
94
- BazModel.grape_documentation(self)
134
+ desc 'baz models index'
135
+ params do
136
+ BazModel.grape_documentation(self)
137
+ end
138
+ get '/' do
139
+ BazModel.with_params(params)
95
140
  end
96
- get '/' do
97
- BazModel.with_params(params)
141
+ end
142
+ end
143
+
144
+ module ApiHelpers
145
+ include Rack::Test::Methods
146
+ def app
147
+ api = Rack::Builder.new do
148
+ run TestApi
98
149
  end
150
+ Rack::URLMap.new('/api' => api)
99
151
  end
100
152
 
153
+ def time
154
+ Time.current.utc.strftime("%Y-%m-%d %H:%M:%S")
155
+ end
101
156
  end
102
157
 
158
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpsql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Champon
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-07-24 00:00:00.000000000 Z
13
+ date: 2013-07-31 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -152,6 +152,34 @@ dependencies:
152
152
  - - '>='
153
153
  - !ruby/object:Gem::Version
154
154
  version: '0'
155
+ - !ruby/object:Gem::Dependency
156
+ name: timecop
157
+ requirement: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - '>='
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ type: :development
163
+ prerelease: false
164
+ version_requirements: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - '>='
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ - !ruby/object:Gem::Dependency
170
+ name: rack-test
171
+ requirement: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - '>='
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ type: :development
177
+ prerelease: false
178
+ version_requirements: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - '>='
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
155
183
  description: Expose model columns and ARel methods through query parameters in grape
156
184
  end points
157
185
  email:
@@ -176,6 +204,7 @@ files:
176
204
  - httpsql.gemspec
177
205
  - lib/httpsql.rb
178
206
  - lib/httpsql/version.rb
207
+ - test/api_test.rb
179
208
  - test/httpsql_test.rb
180
209
  - test/test_helper.rb
181
210
  - tmp/.gitkeep
@@ -205,5 +234,6 @@ specification_version: 4
205
234
  summary: Select model specified fields, create arbitrary queries, all using CGI query
206
235
  parameters
207
236
  test_files:
237
+ - test/api_test.rb
208
238
  - test/httpsql_test.rb
209
239
  - test/test_helper.rb