httpsql 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # Httpsql
2
2
 
3
- TODO: Write a gem description
3
+ Httpsql is a module, designed to be included in [Active Record](http://api.rubyonrails.org/classes/ActiveRecord/Base.html)
4
+ models exposed by [grape](https://github.com/intridea/grape). Once the module is
5
+ included, a given model can respond directly to query params passed to it, using
6
+ `where_params_eq`. You can also constrain the fields returned by the model,
7
+ using the `fields` query parameter.
8
+
9
+ Httpsql uses [ARel](http://www.slideshare.net/flah00/activerecord-arel) to
10
+ generate queries and exposes ARel's methods via query params. The supported ARel
11
+ methods are eq, not_eq, matches, does_not_match, gt, gteq, lt, lteq.
12
+
13
+ Httpsql also generates documentaion for endpoints, which can be easily merged
14
+ into your existing documentation (`#route_params`).
15
+
16
+ Httpsql reserves one parameter, access_token. If your model has a field called
17
+ access_token, you'll need to rename it.
4
18
 
5
19
  ## Installation
6
20
 
@@ -18,12 +32,81 @@ Or install it yourself as:
18
32
 
19
33
  ## Usage
20
34
 
21
- TODO: Write usage instructions here
35
+ Assume you have a model, widget, whose fields are id, int_field, string_field, created_at, updated_at.
36
+
37
+ create_table "widgets", :force => true do |t|
38
+ t.integer "int_field"
39
+ t.string "string_field"
40
+ t.datetime "created_at", :null => false
41
+ t.datetime "updated_at", :null => false
42
+ end
43
+
44
+ ### model.rb
45
+
46
+ class Widget < ActiveRecord::Base
47
+ include Httpsql
48
+ attr_accessible :int_field, :string_field
49
+ end
50
+
51
+ ### api.rb
52
+
53
+ class Api < Grape::API
54
+ version 'v1'
55
+ logger Rails.logger
56
+ default_format :json
57
+
58
+ resource :widgets do
59
+ desc 'Get all widgets', {
60
+ optional_params: Widget.route_params
61
+ }
62
+ get '/' do
63
+ present Widget.where_params_eq(params)
64
+ end
65
+ end
66
+ end
67
+
68
+ ### config.ru
69
+
70
+ api = Rack::Builder.new do
71
+ run Api
72
+ end
73
+ run Rack::URLMap.new \
74
+ '/' => YourApp::Application,
75
+ '/api' => api
76
+
77
+ Now you're able to run your app
78
+
79
+ rails s
80
+
81
+ Query your new API
82
+
83
+ curl 'http://localhost:3000/api/v1/widgets'
84
+ SELECT * FROM widgets
85
+
86
+ curl 'http://localhost:3000/api/v1/widgets?id=1'
87
+ SELECT * FROM widgets WHERE id = 1
88
+
89
+ curl 'http://localhost:3000/api/v1/widgets?id[]=1&id[]=2'
90
+ SELECT * FROM widgets WHERE id IN (1,2)
91
+
92
+ curl 'http://localhost:3000/api/v1/widgets?id.gt=10&id.lt=100'
93
+ SELECT * FROM widgets WHERE id > 10 AND id < 100
94
+
95
+ curl 'http://localhost:3000/api/v1/widgets?id[]=1&id[]=2&created_at.gt=2013-06-01'
96
+ SELECT * FROM widgets WHERE id IN (1,2) AND created_at > '2013-06-01'
97
+
98
+ curl 'http://localhost:3000/api/v1/widgets?id[]=1&id[]=2&created_at.gt=2013-06-01&fields[]=id&fields[]=int_field'
99
+ SELECT id, int_field FROM widgets WHERE id IN (1,2) AND created_at > '2013-06-01'
100
+
101
+ curl 'http://localhost:3000/api/v1/describe_api'
102
+ Returns JSON describing the API
22
103
 
23
104
  ## Contributing
24
105
 
25
106
  1. Fork it
26
107
  2. Create your feature branch (`git checkout -b my-new-feature`)
27
108
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
109
+ * write some god damned tests
110
+ 4. Run your god damned tests (`rake test`)
111
+ 5. Push to the branch (`git push origin my-new-feature`)
112
+ 6. Create new Pull Request
data/Rakefile CHANGED
@@ -1 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs.push "lib"
6
+ t.test_files = FileList["test/*_test.rb"]
7
+ t.verbose = true
8
+ end
data/httpsql.gemspec CHANGED
@@ -6,11 +6,11 @@ require 'httpsql/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "httpsql"
8
8
  spec.version = Httpsql::VERSION
9
- spec.authors = ["Sean Shillo", "Alejandro Ciniglio"]
10
- spec.email = ["sean@adaptly.com", "alejandro@adaptly.com"]
11
- spec.description = %q{Do sql over http}
12
- spec.summary = %q{httpsql}
13
- spec.homepage = ""
9
+ spec.authors = [ "Philip Champon", "Alejandro Ciniglio", "Sean Shillo" ]
10
+ spec.email = [ "philip@adaptly.com", "alejandro@adaptly.com", "sean@adaptly.com" ]
11
+ spec.description = %q{Expose model columns and ARel methods through query parameters in grape end points}
12
+ spec.summary = %q{Select model specified fields, create arbitrary queries, all using CGI query parameters}
13
+ spec.homepage = "https://github.com/Adaptly/httpsql"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
@@ -20,5 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
- spec.add_dependency "activesupport"
23
+ spec.add_dependency "activerecord", ">= 3.0"
24
+ spec.add_dependency "grape"
25
+ spec.add_development_dependency "sqlite3"
24
26
  end
data/lib/httpsql.rb CHANGED
@@ -1,42 +1,47 @@
1
1
  require "httpsql/version"
2
2
 
3
3
  module Httpsql
4
- extend ActiveSupport::Concern
5
- included do
6
- class << self
7
- def valid_params(params)
8
- params.select{|k,v| column_names.include?(k.to_s.split('.').first)}
9
- end
10
-
11
- def where_params_eq(params={})
12
-
13
- cond = valid_params(params).map do |k,v|
14
- next if k.to_s == 'access_token'
15
- (k, m) = k.to_s.split('.')
16
- if m
17
- arel_table[k.to_sym].send(m.to_sym, v)
18
- elsif v.respond_to?(:any?)
19
- arel_table[k.to_sym].in(v)
20
- else
21
- arel_table[k.to_sym].eq(v)
22
- end
23
- end.inject{|x,y| x.and(y)}
24
-
25
- where(cond)
26
- end
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
27
7
 
28
- def route_params
29
- columns.inject({}) do |m,c|
30
- m[c.name] = {
31
- type: c.sql_type,
32
- desc: c.name,
33
- primary: c.primary
34
- }
35
- m
8
+ module ClassMethods
9
+ def where_params_eq(params={})
10
+ cond = valid_params(params).map do |k,v|
11
+ (k, m) = k.to_s.split('.')
12
+ next if k.to_s == 'access_token'
13
+ if m
14
+ arel_table[k.to_sym].send(m.to_sym, v)
15
+ elsif v.respond_to?(:any?)
16
+ arel_table[k.to_sym].in(v)
17
+ else
18
+ arel_table[k.to_sym].eq(v)
36
19
  end
37
- end
20
+ end.inject{|x,y| x.and(y)}
38
21
 
22
+ ar_rel = where(cond)
23
+ ar_rel = ar_rel.select(params[:field]) if params[:field]
24
+ ar_rel
25
+ end
26
+
27
+ def route_params
28
+ columns.inject({}) do |m,c|
29
+ m[c.name] = {
30
+ type: c.sql_type,
31
+ desc: c.name,
32
+ primary: c.primary
33
+ }
34
+ m
35
+ end.merge "field" => {
36
+ type: 'array',
37
+ desc: 'select fields',
38
+ primary: false
39
+ }
40
+ end
39
41
 
42
+ private
43
+ def valid_params(params)
44
+ params.select{|k,v| column_names.include?(k.to_s.split('.').first)}
40
45
  end
41
46
  end
42
47
  end
@@ -1,3 +1,3 @@
1
1
  module Httpsql
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,126 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+ require 'active_record'
4
+ require 'httpsql'
5
+
6
+ ActiveRecord::Base.configurations[:test] = {adapter: 'sqlite3', database: 'tmp/httpsql_test'}
7
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[:test])
8
+ ActiveRecord::Base.connection.execute %Q{ DROP TABLE IF EXISTS foo_models }
9
+ ActiveRecord::Base.connection.execute %Q{
10
+ CREATE TABLE foo_models (
11
+ id integer,
12
+ int_field integer,
13
+ string_field text,
14
+ access_token text,
15
+ created_at text default CURRENT_TIMESTAMP,
16
+ updated_at text default CURRENT_TIMESTAMP,
17
+ primary key(id)
18
+ );
19
+ }
20
+ class FooModel < ActiveRecord::Base
21
+ include Httpsql
22
+ attr_accessible :int_field, :string_field, :access_token
23
+ end
24
+
25
+ def generate_models
26
+ FooModel.create!([
27
+ {int_field: 0, string_field: "zero", access_token: "000"},
28
+ {int_field: 1, string_field: "one", access_token: "111"},
29
+ {int_field: 2, string_field: "two", access_token: "222"},
30
+ {int_field: 3, string_field: "three", access_token: "333"},
31
+ {int_field: 4, string_field: "four", access_token: "444"},
32
+ ])
33
+ end
34
+
35
+ describe Httpsql do
36
+ before :each do
37
+ FooModel.connection.execute %Q{DELETE FROM foo_models}
38
+ end
39
+
40
+ it 'selects a model\'s columns from a given hash' do
41
+ ret = FooModel.send(:valid_params, id: 1, int_field: 2, string_field: "foo", access_token: "a", created_at: '2013-01-01T00:00:00', created_at: '2013-01-01T00:00:00', foo: :bar)
42
+ ret.must_equal(id: 1, int_field: 2, string_field: "foo", access_token: "a", created_at: '2013-01-01T00:00:00', created_at: '2013-01-01T00:00:00')
43
+ end
44
+
45
+ it 'selects all models' do
46
+ models = generate_models
47
+ FooModel.where_params_eq({}).must_equal models
48
+ end
49
+
50
+ it 'selects a specified array of models' do
51
+ models = generate_models
52
+ FooModel.where_params_eq("int_field" => [0, 1]).must_equal models[0..1]
53
+ end
54
+
55
+ it 'selects a model, using eq' do
56
+ models = generate_models
57
+ FooModel.where_params_eq("int_field.eq" => 0).must_equal [models[0]]
58
+ end
59
+
60
+ it 'selects models, using not_eq' do
61
+ models = generate_models
62
+ FooModel.where_params_eq("int_field.not_eq" => 0).must_equal models[1..-1]
63
+ end
64
+
65
+ it 'selects a model, using matches' do
66
+ models = generate_models
67
+ FooModel.where_params_eq("string_field.matches" => "%hre%").must_equal [models[3]]
68
+ end
69
+
70
+ it 'selects models, using does_not_match' do
71
+ models = generate_models
72
+ FooModel.where_params_eq("string_field.does_not_match" => "%ero").must_equal models[1..-1]
73
+ end
74
+ it 'selects models, using gt' do
75
+ models = generate_models
76
+ FooModel.where_params_eq("int_field.gt" => 1).must_equal models[2..-1]
77
+ end
78
+
79
+ it 'selects models, using gteq' do
80
+ models = generate_models
81
+ FooModel.where_params_eq("int_field.gteq" => 2).must_equal models[2..-1]
82
+ end
83
+
84
+ it 'select models, using lt' do
85
+ models = generate_models
86
+ FooModel.where_params_eq("int_field.lt" => 1).must_equal [models[0]]
87
+ end
88
+
89
+ it 'selects models, using lteq' do
90
+ models = generate_models
91
+ FooModel.where_params_eq("int_field.lteq" => 2).must_equal models[0..2]
92
+ end
93
+
94
+ it 'selects models, using two ARel methods' do
95
+ models = generate_models
96
+ FooModel.where_params_eq("int_field.gteq" => 1, "id.gt" => 4).must_equal [models[4]]
97
+ end
98
+
99
+ it 'ignores access_token' do
100
+ models = generate_models
101
+ FooModel.where_params_eq("access_token" => "111").must_equal models
102
+ end
103
+
104
+ it 'ignores access_token dot notation' do
105
+ models = generate_models
106
+ FooModel.where_params_eq("access_token.eq" => "111").must_equal models
107
+ end
108
+
109
+ it 'selects a model with specified fields' do
110
+ generate_models
111
+ model = FooModel.select([:int_field, :id]).where(int_field: 0)
112
+ FooModel.where_params_eq("int_field.eq" => 0, field: [:int_field, :id]).must_equal model
113
+ end
114
+
115
+ it 'generates the correct documentation' do
116
+ FooModel.route_params.must_equal({
117
+ "id" => {:type => "integer", :desc => "id", :primary => true},
118
+ "int_field" => {:type => "integer", :desc => "int_field", :primary => false},
119
+ "string_field" => {:type => "text", :desc => "string_field", :primary => false},
120
+ "access_token" => {:type => "text", :desc => "access_token", :primary => false},
121
+ "created_at" => {:type => "text", :desc => "created_at", :primary => false},
122
+ "updated_at" => {:type => "text", :desc => "updated_at", :primary => false},
123
+ "field" => {:type => "array", :desc => "select fields", :primary => false}}
124
+ )
125
+ end
126
+ end
metadata CHANGED
@@ -1,19 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpsql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
- - Sean Shillo
8
+ - Philip Champon
8
9
  - Alejandro Ciniglio
10
+ - Sean Shillo
9
11
  autorequire:
10
12
  bindir: bin
11
13
  cert_chain: []
12
- date: 2013-06-27 00:00:00.000000000 Z
14
+ date: 2013-07-03 00:00:00.000000000 Z
13
15
  dependencies:
14
16
  - !ruby/object:Gem::Dependency
15
17
  name: bundler
16
18
  requirement: !ruby/object:Gem::Requirement
19
+ none: false
17
20
  requirements:
18
21
  - - ~>
19
22
  - !ruby/object:Gem::Version
@@ -21,6 +24,7 @@ dependencies:
21
24
  type: :development
22
25
  prerelease: false
23
26
  version_requirements: !ruby/object:Gem::Requirement
27
+ none: false
24
28
  requirements:
25
29
  - - ~>
26
30
  - !ruby/object:Gem::Version
@@ -28,35 +32,73 @@ dependencies:
28
32
  - !ruby/object:Gem::Dependency
29
33
  name: rake
30
34
  requirement: !ruby/object:Gem::Requirement
35
+ none: false
31
36
  requirements:
32
- - - '>='
37
+ - - ! '>='
33
38
  - !ruby/object:Gem::Version
34
39
  version: '0'
35
40
  type: :development
36
41
  prerelease: false
37
42
  version_requirements: !ruby/object:Gem::Requirement
43
+ none: false
38
44
  requirements:
39
- - - '>='
45
+ - - ! '>='
40
46
  - !ruby/object:Gem::Version
41
47
  version: '0'
42
48
  - !ruby/object:Gem::Dependency
43
- name: activesupport
49
+ name: activerecord
50
+ requirement: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '3.0'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '3.0'
64
+ - !ruby/object:Gem::Dependency
65
+ name: grape
44
66
  requirement: !ruby/object:Gem::Requirement
67
+ none: false
45
68
  requirements:
46
- - - '>='
69
+ - - ! '>='
47
70
  - !ruby/object:Gem::Version
48
71
  version: '0'
49
72
  type: :runtime
50
73
  prerelease: false
51
74
  version_requirements: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ - !ruby/object:Gem::Dependency
81
+ name: sqlite3
82
+ requirement: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ none: false
52
92
  requirements:
53
- - - '>='
93
+ - - ! '>='
54
94
  - !ruby/object:Gem::Version
55
95
  version: '0'
56
- description: Do sql over http
96
+ description: Expose model columns and ARel methods through query parameters in grape
97
+ end points
57
98
  email:
58
- - sean@adaptly.com
99
+ - philip@adaptly.com
59
100
  - alejandro@adaptly.com
101
+ - sean@adaptly.com
60
102
  executables: []
61
103
  extensions: []
62
104
  extra_rdoc_files: []
@@ -69,28 +111,32 @@ files:
69
111
  - httpsql.gemspec
70
112
  - lib/httpsql.rb
71
113
  - lib/httpsql/version.rb
72
- homepage: ''
114
+ - test/httpsql_test.rb
115
+ homepage: https://github.com/Adaptly/httpsql
73
116
  licenses:
74
117
  - MIT
75
- metadata: {}
76
118
  post_install_message:
77
119
  rdoc_options: []
78
120
  require_paths:
79
121
  - lib
80
122
  required_ruby_version: !ruby/object:Gem::Requirement
123
+ none: false
81
124
  requirements:
82
- - - '>='
125
+ - - ! '>='
83
126
  - !ruby/object:Gem::Version
84
127
  version: '0'
85
128
  required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
86
130
  requirements:
87
- - - '>='
131
+ - - ! '>='
88
132
  - !ruby/object:Gem::Version
89
133
  version: '0'
90
134
  requirements: []
91
135
  rubyforge_project:
92
- rubygems_version: 2.0.0.rc.2
136
+ rubygems_version: 1.8.23
93
137
  signing_key:
94
- specification_version: 4
95
- summary: httpsql
96
- test_files: []
138
+ specification_version: 3
139
+ summary: Select model specified fields, create arbitrary queries, all using CGI query
140
+ parameters
141
+ test_files:
142
+ - test/httpsql_test.rb
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 9fd889994ae94496fde8767ceb1aeedb6bf7b580
4
- data.tar.gz: 9d54f043cfac281572b16ba80d02379d142824a4
5
- SHA512:
6
- metadata.gz: 47fd7364530a7db44688280d83c3c8f23a5f72379b9b2948abf3beb1a9f6f87b21bff82cd5fc7757362886db53ce79150ddef029b360a1268d7a8358a6c7590c
7
- data.tar.gz: b7ca268a828be986d6b1186c411af54af0e4bd4b1f997a563c0abb487b827168ef1d9387d8ddc2d9072d9be4b311eb2096951887c6233ee36d6eadd2f3aba8f7