httpsql 0.0.1 → 0.1.0

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.
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