gros_calin 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b84400b70034e273680e1f01aaaa4db3ff19ffc9
4
+ data.tar.gz: 8fe9102cc4394b78aa0dd3f61c97db7600effc83
5
+ SHA512:
6
+ metadata.gz: ba4e596de0a7d661e85866d2bf81bef0af8da0d9f2eee1e7404b15213477254ab0c83c28c9ec1c467e5de0945b8ce08d35cc39bf14ac3a047a5cd4742d0df29a
7
+ data.tar.gz: 228a27a22e4113257426183c6aa8182a04255dd2ec887b5477f2b508d28f97bc7bcf925c1f3c0f98c1f2f46a27d9eac76612ca937446aa9bc15c2f1d123b35d0
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.1
6
+ - 2.1.5
7
+ notifications:
8
+ hipchat:
9
+ rooms:
10
+ secure: NvtShOCmF+gKzPfUG5Gsd+Ibf5EnVtl1IQD033iRG1rOzSMejp64CYiRMQ3xjEpSKMZJ9w08Lem0WQyafEe5FbiHvDWb0nbHh60wj5QILeRb6Ajf5htSGWulZtR2yr9NIhxqfEZveXXLiH4xuqLxQ5rvM0jpt7enUTKKQFcRjSk=
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in gros_calin.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ guard :minitest do
2
+ # with Minitest::Spec
3
+ watch(%r{^spec/(.*)_spec\.rb$})
4
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
5
+ watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
6
+ watch(%r{^spec/support/(.*)\.rb$}) { 'spec' }
7
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 ServeBox
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # Gros-Câlin
2
+
3
+ Share your database queries via HTTP/JSON.
4
+
5
+ [![Build Status](https://travis-ci.org/servebox/gros_calin.png)](https://travis-ci.org/servebox/gros_calin)
6
+ [![Dependency Status](https://gemnasium.com/servebox/gros_calin.png)](https://gemnasium.com/servebox/gros_calin)
7
+ [![Coverage Status](https://coveralls.io/repos/servebox/gros_calin/badge.png)](https://coveralls.io/r/servebox/gros_calin)
8
+ [![Code Climate](https://codeclimate.com/github/servebox/gros_calin.png)](https://codeclimate.com/github/servebox/gros_calin)
9
+
10
+ ## Installation
11
+
12
+ Install the gem:
13
+
14
+ $ gem install gros_calin
15
+
16
+ Depending on the drivers you'd like to use to connect your databases, you'll
17
+ also have to install one or more of the following gems (see the
18
+ _Available drivers_ section below):
19
+
20
+ * mysql2 (MySQL)
21
+ * moped (MongoDB)
22
+
23
+ ## Usage
24
+
25
+ Create a configuration file (see the _Configuration_ section below), then start
26
+ the server:
27
+
28
+ $ gros_calin
29
+
30
+ If you want to place the process in the background:
31
+
32
+ $ gros_calin -d
33
+
34
+ By default, the server searches for a `config.yml` file in the current
35
+ directory. To point to a specific location, use the `-c` flag:
36
+
37
+ $ gros_calin -c /path/to/config.yml
38
+
39
+ The server binds to the port `3313`, use the `-p` flag to change the port
40
+ number:
41
+
42
+ $ gros_calin -p 8080
43
+
44
+ To stop the server, hit `Ctrl^C` or, if you placed it in the background:
45
+
46
+ $ gros_calin stop
47
+
48
+ ## Configuration
49
+
50
+ Configuration takes place in a [YAML](http://en.wikipedia.org/wiki/YAML) file.
51
+ First declare your datasources using one of the available drivers, then list the
52
+ queries you'd like to give a hug.
53
+
54
+ ### HTTP endpoints
55
+
56
+ * List available datasources: `http://localhost:3313/`
57
+ * List available hugs for a datasource: `http://localhost:3313/<datasource>`
58
+ * Query a specific hug: `http://localhost:3313/<datasource>/<hug>`
59
+
60
+ ### Available drivers
61
+
62
+ #### MySQL
63
+
64
+ ```yaml
65
+ myapp_datasource:
66
+ driver: "mysql"
67
+ options:
68
+ host: "db.example.com"
69
+ username: "operator"
70
+ password: "secret"
71
+ database: "myapp"
72
+ hugs:
73
+ sign_ups: "SELECT count(id) AS sign_ups, country_code FROM users WHERE created_at DATE_SUB( NOW(), INTERVAL 24 HOUR) GROUP by country_code;"
74
+ # another_datasource:
75
+ ```
76
+
77
+ See the [mysql2](https://github.com/brianmario/mysql2) client for a list
78
+ of [available connection
79
+ options](https://github.com/brianmario/mysql2#connection-options).
80
+
81
+ #### MongoDB
82
+
83
+ ```yaml
84
+ projects_datasource:
85
+ driver: "mongodb"
86
+ options:
87
+ hosts:
88
+ - replset1.example.com:27017
89
+ - replset2.example.com:27017
90
+ - replset3.example.com:27017
91
+ username: "operator"
92
+ password: "secret"
93
+ hugs:
94
+ issues: "db.projects.find( { score: { $lt: 3.8 } } ).sort(score: -1).toArray()"
95
+ status: "{ failures: db.projects.find( { status: 'failed' } ).count(), success: db.projects.find( { status: 'success' } ).count() }"
96
+ average_score: "db.builds.aggregate( { $group: { _id: '$project_id', builds: { $avg: '$score' } } }).result"
97
+ # another_datasource:
98
+ ```
99
+
100
+ ## Frequently asked questions
101
+
102
+ ### Is it fast?
103
+
104
+ Well, it depends.
105
+
106
+ ### Does it scale?
107
+
108
+ It can serves up to a billion RPM (you'll have to rewrite it in Erlang, though).
109
+
110
+ ### "Gros-Câlin"?
111
+
112
+ Gros-Câlin (_Big Cuddle_) is named after the eponymous novel written by Romain
113
+ Gary under the pen name Émile Ajar. In the book, M. Cousin, a statistician,
114
+ lives with a python which hugs him tight and helps him cope with loneliness.
115
+
116
+ ![Romain Gary](https://raw.github.com/servebox/gros_calin/master/romain-gary.jpg)
117
+
118
+ ## Contributing
119
+
120
+ 1. Fork it
121
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
122
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
123
+ 4. Push to the branch (`git push origin my-new-feature`)
124
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'spec'
7
+ t.pattern = "spec/**/*_spec.rb"
8
+ end
9
+
10
+ task :default => [:test]
data/bin/gros_calin ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'gros_calin/cli'
3
+
4
+ # See https://github.com/erikhuda/thor/issues/244
5
+ Thor.send :define_singleton_method, :exit_on_failure? do true end
6
+ GrosCalin::CLI.start(ARGV)
data/config.ru ADDED
@@ -0,0 +1,5 @@
1
+ require 'gros_calin'
2
+
3
+ GrosCalin::Application.set :config,
4
+ GrosCalin::Config.new(ENV['GROS_CALIN_CONFIG'])
5
+ run GrosCalin::Application
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gros_calin/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "gros_calin"
8
+ spec.version = GrosCalin::VERSION
9
+ spec.authors = ["Jef Mathiot"]
10
+ spec.email = ["jeff.mathiot@gmail.com"]
11
+ spec.summary = %q{Make database queries available via HTTP}
12
+ spec.description = %q{Make database queries available via HTTP}
13
+ spec.homepage = "https://github.com/servebox/gros_calin"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'mocha', '~> 1.1', '>= 1.1.0'
24
+ spec.add_development_dependency 'coveralls', '~> 0.7', '>= 0.7.1'
25
+ spec.add_development_dependency 'minitest', '~> 5.4', '>= 5.4.1'
26
+ spec.add_development_dependency 'minitest-implicit-subject', '~> 1.4', '>= 1.4.0'
27
+ spec.add_development_dependency 'rb-readline', '~> 0.5', '>= 0.5.0'
28
+ spec.add_development_dependency 'guard', '~> 2.11', '>= 2.11.1'
29
+ spec.add_development_dependency 'guard-minitest', '~> 2.3', '>= 2.3.2'
30
+
31
+ spec.add_development_dependency 'mysql2', '~> 0.3', '>= 0.3.18'
32
+ spec.add_development_dependency 'moped', '~> 2.0', '>= 2.0.3'
33
+
34
+ spec.add_dependency 'sinatra', '~> 1.4', '>= 1.4.5'
35
+ spec.add_dependency 'sinatra-contrib', '~> 1.4', '>= 1.4.2'
36
+ spec.add_dependency 'rack', '~> 1.6', '>= 1.6.0'
37
+ spec.add_dependency 'thin', '~> 1.6', '>= 1.6.3'
38
+ spec.add_dependency 'thor', '~> 0.19', '>= 0.19.1'
39
+
40
+ end
@@ -0,0 +1,42 @@
1
+ require 'thor'
2
+
3
+ module GrosCalin
4
+
5
+ class CLI < Thor
6
+
7
+ desc "start", "Starts the server"
8
+ option :port, aliases: %w(p), type: :numeric, default: '3313',
9
+ desc: "The port number to assign"
10
+ option :daemon, aliases: %w(d), type: :boolean, default: false,
11
+ desc: "Place the server process in the background"
12
+ option :config, aliases: %w(-c), type: :string,
13
+ desc: 'Override path to configuration file', default: './config.yml'
14
+
15
+ def start
16
+ command = "thin -R #{config_ru} start -p #{options[:port]}"
17
+ if options[:config]
18
+ command.prepend "export GROS_CALIN_CONFIG=#{options[:config]}; "
19
+ end
20
+ command << ' -d' if options[:daemon]
21
+ system command
22
+ end
23
+
24
+ desc "stop", "Stops the server"
25
+
26
+ def stop
27
+ system "thin stop"
28
+ end
29
+
30
+ default_task :start
31
+
32
+ no_tasks do
33
+
34
+ def config_ru
35
+ File.expand_path(File.join(File.dirname(__FILE__), '../../config.ru'))
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,103 @@
1
+ require 'yaml'
2
+
3
+ module GrosCalin
4
+
5
+ class Collection < Array
6
+
7
+ def json
8
+ map(&:json)
9
+ end
10
+
11
+ def get(id)
12
+ find{ |item| item.respond_to?(:id) && item.id == id }
13
+ end
14
+
15
+ end
16
+
17
+ module Identifier
18
+
19
+ def self.included(base)
20
+ base.send :attr_reader, :id
21
+ end
22
+
23
+ end
24
+
25
+ class Datasource
26
+ include Identifier
27
+
28
+ attr_reader :driver
29
+
30
+ def initialize(id, driver, options={})
31
+ @id = id
32
+ @driver = driver.new(options)
33
+ end
34
+
35
+ def hugs
36
+ @hugs ||= Collection.new
37
+ end
38
+
39
+ def json
40
+ {
41
+ id: id,
42
+ driver: driver.id,
43
+ uri: "/#{id}"
44
+ }
45
+ end
46
+
47
+ end
48
+
49
+ class Hug
50
+ include Identifier
51
+
52
+ attr_reader :datasource, :query
53
+
54
+ def initialize(id, datasource, query)
55
+ @id = id
56
+ @datasource = datasource
57
+ @query = query
58
+ end
59
+
60
+ def json
61
+ {
62
+ id: id,
63
+ datasource: datasource.json,
64
+ uri: "/#{datasource.id}/#{id}"
65
+ }
66
+ end
67
+
68
+ def results
69
+ datasource.driver.query(id, @query)
70
+ end
71
+
72
+ end
73
+
74
+ class Config
75
+
76
+ def initialize(config='./config.yml')
77
+ yaml = load(config)
78
+ raise "Invalid YAML file #{config}" unless yaml
79
+ yaml.each do |id, ds|
80
+ datasource = Datasource.new(id, driver(ds['driver']), ds['options'])
81
+ (ds['hugs'] || []).each do |id, query|
82
+ datasource.hugs << Hug.new( id, datasource, query )
83
+ end
84
+ datasources << datasource
85
+ end
86
+ end
87
+
88
+ def datasources
89
+ @datasources ||= Collection.new
90
+ end
91
+
92
+ private
93
+ def load(config)
94
+ YAML.load(File.read(File.expand_path(config)))
95
+ end
96
+
97
+ def driver(id)
98
+ GrosCalin.drivers[id]
99
+ end
100
+
101
+ end
102
+
103
+ end
@@ -0,0 +1,39 @@
1
+ module GrosCalin
2
+
3
+ class << self
4
+ def drivers
5
+ @drivers ||= {}
6
+ end
7
+ end
8
+
9
+ module Driver
10
+ attr_reader :options
11
+
12
+ def initialize(options={})
13
+ @options = options
14
+ end
15
+
16
+ def id
17
+ self.class.id
18
+ end
19
+
20
+ def self.included(base)
21
+ base.extend(ClassMethods)
22
+ end
23
+
24
+ module ClassMethods
25
+
26
+ attr_reader :id
27
+
28
+ def register(id)
29
+ GrosCalin.drivers[@id = id] = self
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ require "gros_calin/mysql"
39
+ require "gros_calin/mongo_db"
@@ -0,0 +1,48 @@
1
+ module GrosCalin
2
+
3
+ begin
4
+
5
+ require 'moped'
6
+
7
+ class MongoDB
8
+ include Driver
9
+
10
+ register 'mongodb'
11
+
12
+ def query(id, js)
13
+ with_session do |session|
14
+ cmd = {'$eval' => "function(){ return #{js}; }", nolock: true}
15
+ session.command(cmd)['retval']
16
+ end
17
+ end
18
+
19
+ protected
20
+
21
+ def with_session(&block)
22
+ session = Moped::Session.new(@options['hosts'] || ['127.0.0.1:27017'])
23
+ session.use @options['database']
24
+ if @options['username']
25
+ session.login(@options['username'], @options['password'])
26
+ end
27
+ yield session
28
+ end
29
+
30
+ def mandatory(options, attr)
31
+ raise "\"#{attr}\" has not been specified" unless options[attr]
32
+ options[attr]
33
+ end
34
+
35
+ def whitelist(query)
36
+ query.select{|descriptor|
37
+ name = descriptor.is_a?(Hash) && descriptor.keys.first || descriptor
38
+ ALLOWED_METHODS.include?(name)
39
+ }
40
+ end
41
+
42
+ end
43
+
44
+ rescue
45
+ puts "Unable to find the moped gem, driver for MongoDB won't load."
46
+ end
47
+
48
+ end
@@ -0,0 +1,20 @@
1
+ module GrosCalin
2
+
3
+ begin
4
+
5
+ require 'mysql2'
6
+
7
+ class Mysql
8
+ include Driver
9
+ register 'mysql'
10
+
11
+ def query(id, sql)
12
+ Mysql2::Client.new(@options).query(sql).each
13
+ end
14
+ end
15
+
16
+ rescue
17
+ puts "Unable to find the mysql2 gem, driver for MySQL won't load."
18
+ end
19
+
20
+ end
@@ -0,0 +1,3 @@
1
+ module GrosCalin
2
+ VERSION = "0.0.1"
3
+ end
data/lib/gros_calin.rb ADDED
@@ -0,0 +1,61 @@
1
+ require "gros_calin/version"
2
+ require "gros_calin/config"
3
+ require "gros_calin/driver"
4
+ require "sinatra/base"
5
+ require "sinatra/json"
6
+
7
+ module GrosCalin
8
+
9
+ class Application < Sinatra::Base
10
+ helpers Sinatra::JSON
11
+
12
+ set :show_exceptions, false
13
+
14
+ def config
15
+ settings.config
16
+ end
17
+
18
+ def datasource(id)
19
+ config.datasources.get(id).tap do |datasource|
20
+ unless datasource
21
+ raise Sinatra::NotFound.new("Unknown datasource with id \"#{id}\"")
22
+ end
23
+ end
24
+ end
25
+
26
+ def hug(datasource, id)
27
+ datasource(datasource).hugs.get(id).tap do |hug|
28
+ unless hug
29
+ raise Sinatra::NotFound.new("Unknown hug with identifier \"#{id}\" " +
30
+ "for datasource \"#{datasource}\"")
31
+ end
32
+ end
33
+ end
34
+
35
+ not_found do
36
+ env['sinatra.error'].message
37
+ end
38
+
39
+ error do
40
+ 'Something went wrong: ' + env['sinatra.error'].message
41
+ end
42
+
43
+ # List the available datasources
44
+ get '/' do
45
+ json config.datasources.json
46
+ end
47
+
48
+ # Show the details and available hugs for a given datasource
49
+ get '/:datasource' do
50
+ json datasource(params[:datasource]).hugs.map(&:json)
51
+ end
52
+
53
+
54
+ # Query a specific hug
55
+ get '/:datasource/:hug' do
56
+ json hug(params[:datasource], params[:hug]).results
57
+ end
58
+
59
+ end
60
+
61
+ end
data/romain-gary.jpg ADDED
Binary file
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+
3
+ describe GrosCalin::Collection do
4
+
5
+ let(:collection){ subject.new }
6
+
7
+ describe 'retrieving items by id' do
8
+
9
+ it 'finds the item' do
10
+ collection << mock(id: 1)
11
+ collection << item = mock(id: 2)
12
+ collection.get(2).must_equal item
13
+ end
14
+
15
+ it 'ignores items which do not respond to id' do
16
+ collection << mock
17
+ collection << item = mock(id: 2)
18
+ collection.get(2).must_equal item
19
+ end
20
+
21
+ end
22
+
23
+ it 'includes children when building a serializable hash' do
24
+ collection << mock(json: { id: 1 })
25
+ collection.json.must_equal [{id: 1}]
26
+ end
27
+
28
+ end
29
+
30
+ ConfigDriverKlazz = Class.new do
31
+ include GrosCalin::Driver
32
+
33
+ register 'driver_id'
34
+ end
35
+
36
+ describe GrosCalin::Datasource do
37
+
38
+ let(:datasource){
39
+ GrosCalin::Datasource.new('dsid', ConfigDriverKlazz, {an_option: 1})
40
+ }
41
+
42
+ let(:datasource_hash){
43
+ {id: 'dsid', driver: 'driver_id', uri: '/dsid'}
44
+ }
45
+
46
+ it 'initializes the driver with options' do
47
+ datasource.driver.must_be_instance_of ConfigDriverKlazz
48
+ datasource.driver.options.must_equal( an_option: 1 )
49
+ end
50
+
51
+ it 'creates an empty hugs collection' do
52
+ datasource.hugs.must_be_instance_of GrosCalin::Collection
53
+ end
54
+
55
+ it 'builds a serializable hash' do
56
+ datasource.json.must_equal(datasource_hash)
57
+ end
58
+
59
+
60
+ describe GrosCalin::Hug do
61
+
62
+ let(:hug){ subject.new('hugid', datasource, 'select') }
63
+
64
+ it 'builds a serializable hash' do
65
+ hug.json.must_equal( id: 'hugid', datasource: datasource_hash,
66
+ uri: '/dsid/hugid' )
67
+ end
68
+
69
+ it 'queries the driver' do
70
+ datasource.driver.expects(:query).with('hugid', 'select').
71
+ returns(results=[])
72
+ hug.results.must_equal results
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+ describe GrosCalin::Config do
80
+
81
+ let(:empty_file) do
82
+ f = Tempfile.new('config.yml')
83
+ f.close
84
+ f
85
+ end
86
+
87
+ let(:config_file) do
88
+ f = Tempfile.new('config.yml')
89
+ f.write <<-EOF
90
+ ---
91
+ dsid:
92
+ driver: "driver_id"
93
+ options:
94
+ username: "user"
95
+ hugs:
96
+ all: "select"
97
+ EOF
98
+ f.close
99
+ f
100
+ end
101
+
102
+ after do
103
+ [config_file, empty_file].each(&:unlink)
104
+ end
105
+
106
+ it 'defaults to config.yml' do
107
+ subject.any_instance.expects(:load).with('./config.yml').returns([])
108
+ subject.new
109
+ end
110
+
111
+ it 'raises if the config file does not exist' do
112
+ ->{ subject.new('absent.yml')}.must_raise Errno::ENOENT
113
+ end
114
+
115
+ it 'raises if the config file does not exist' do
116
+ ex = ->{ subject.new(empty_file)}.must_raise RuntimeError
117
+ ex.message.must_equal "Invalid YAML file #{empty_file}"
118
+ end
119
+
120
+ it 'loads the provided configuration' do
121
+ config = subject.new(config_file)
122
+ ds = config.datasources.length.must_equal 1
123
+ ds = config.datasources.first
124
+ ds.id.must_equal 'dsid'
125
+ ds.driver.must_be_instance_of ConfigDriverKlazz
126
+ ds.driver.options['username'].must_equal "user"
127
+ ds.hugs.length.must_equal 1
128
+ hug = ds.hugs.first
129
+ hug.id.must_equal 'all'
130
+ hug.datasource.must_equal ds
131
+ hug.query.must_equal 'select'
132
+ end
133
+
134
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe GrosCalin::Driver do
4
+
5
+ DriverKlazz = Class.new do
6
+ include GrosCalin::Driver
7
+
8
+ register 'test_driver'
9
+ end
10
+
11
+ describe DriverKlazz do
12
+
13
+ it 'uses the id it registered with' do
14
+ subject.new.id.must_equal 'test_driver'
15
+ end
16
+
17
+ it 'exposes its initialization options' do
18
+ subject.new(options={an_option: 1}).options.must_equal options
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe GrosCalin::MongoDB do
4
+
5
+ it 'registered as mongodb' do
6
+ GrosCalin.drivers['mongodb'].must_equal subject
7
+ end
8
+
9
+ def expects_session(hosts=['127.0.0.1:27017'])
10
+ ::Moped::Session.expects(:new).with(hosts).
11
+ returns(session=mock)
12
+ session.expects(:use).with('db')
13
+ session
14
+ end
15
+
16
+ def ensure_session_yielded(options={})
17
+ session = nil
18
+ subject.new(options.merge('database'=> 'db')).send(:with_session) do |s|
19
+ session = s
20
+ end
21
+ session.wont_be_nil
22
+ end
23
+
24
+ it 'defaults to localhost' do
25
+ expects_session
26
+ ensure_session_yielded
27
+ end
28
+
29
+ it 'uses the provided hosts' do
30
+ expects_session( hosts = ['host.tld:27018'] )
31
+ ensure_session_yielded('hosts'=>hosts)
32
+ end
33
+
34
+ it 'logs in if a username was provided' do
35
+ session = expects_session ['127.0.0.1:27017']
36
+ session.expects(:login).with('user', 'secret')
37
+ ensure_session_yielded('username'=>'user', 'password'=>'secret')
38
+ end
39
+
40
+ it 'queries the database' do
41
+ session = expects_session ['127.0.0.1:27017']
42
+ js = 'db.collection.find().toArray()'
43
+ session.expects(:command).
44
+ with('$eval' => "function(){ return #{js}; }", nolock: true).
45
+ returns({ 'retval'=>ary=[] })
46
+ subject.new({ 'database' => 'db' }).query('id', js).must_equal ary
47
+ end
48
+
49
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe GrosCalin::Mysql do
4
+
5
+ it 'registered as mysql' do
6
+ GrosCalin.drivers['mysql'].must_equal subject
7
+ end
8
+
9
+ it 'queries the database' do
10
+ Mysql2::Client.expects(:new).with(options={an_option: 1}).
11
+ returns(client=mock)
12
+ client.expects(:query).with('select').returns(result=mock)
13
+ result.expects(:each).returns(data=[])
14
+ subject.new(options).query('id', 'select').must_equal data
15
+ end
16
+
17
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+
4
+ describe GrosCalin::Application do
5
+ include Rack::Test::Methods
6
+
7
+ let(:config){ mock }
8
+ let(:data){ { key: 'value' } }
9
+ let(:body){ '{"key":"value"}' }
10
+
11
+ def app
12
+ subject.set :config, config
13
+ subject
14
+ end
15
+
16
+ it 'renders the error message on error' do
17
+ config.expects(:datasources).raises(RuntimeError, "That's an error")
18
+ get '/'
19
+ last_response.status.must_equal 500
20
+ last_response.body.must_equal "Something went wrong: That's an error"
21
+ end
22
+
23
+ it 'lists datasources' do
24
+ config.expects(:datasources).returns(mock(json: data))
25
+ get '/'
26
+ last_response.status.must_equal 200
27
+ last_response.body.must_equal body
28
+ end
29
+
30
+ def datasources
31
+ config.expects(:datasources).returns(datasources=mock)
32
+ datasources
33
+ end
34
+
35
+ def expects_404(collection, param, url, error_message)
36
+ collection.expects(:get).with(param).returns(nil)
37
+ get url
38
+ last_response.status.must_equal 404
39
+ last_response.body.must_match error_message
40
+ end
41
+
42
+ it 'renders a 404 when it fails to find a specific datasource' do
43
+ expects_404(datasources, 'dsid', '/dsid', /^Unknown datasource/)
44
+ end
45
+
46
+ describe 'with a specific datasource' do
47
+
48
+ let(:datasource){ mock }
49
+
50
+ before do
51
+ datasources.expects(:get).with('dsid').returns(datasource)
52
+ end
53
+
54
+ def ensure_json_array
55
+ last_response.status.must_equal 200
56
+ last_response.body.must_equal "[#{body}]"
57
+ end
58
+
59
+ it 'shows the list of hugs for a datasource' do
60
+ datasource.expects(:hugs).returns([mock(json: data)])
61
+ get '/dsid'
62
+ ensure_json_array
63
+ end
64
+
65
+ def hugs
66
+ datasource.expects(:hugs).returns(hugs=mock)
67
+ hugs
68
+ end
69
+
70
+ it 'renders a specific hug' do
71
+ hugs.expects(:get).with('hugid').returns(mock(results: [data]))
72
+ get '/dsid/hugid'
73
+ ensure_json_array
74
+ end
75
+
76
+ it 'renders a 404 when it fails to find a specific hug' do
77
+ expects_404(hugs, 'hugid', '/dsid/hugid', /^Unknown hug/)
78
+ end
79
+
80
+ end
81
+
82
+ end
@@ -0,0 +1,21 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov::Formatter::HTMLFormatter,
6
+ Coveralls::SimpleCov::Formatter
7
+ ]
8
+
9
+ SimpleCov.start do
10
+ add_filter '/spec/'
11
+ end
12
+
13
+ require 'gros_calin'
14
+ require 'minitest'
15
+ require 'minitest/spec'
16
+ require 'minitest/autorun'
17
+ require 'minitest/pride'
18
+ require 'minitest-implicit-subject'
19
+ require 'mocha/setup'
20
+
21
+ GrosCalin::Application.set :raise_errors, false
metadata ADDED
@@ -0,0 +1,383 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gros_calin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jef Mathiot
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mocha
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 1.1.0
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '1.1'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.1.0
61
+ - !ruby/object:Gem::Dependency
62
+ name: coveralls
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.7'
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 0.7.1
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: '0.7'
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 0.7.1
81
+ - !ruby/object:Gem::Dependency
82
+ name: minitest
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '5.4'
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 5.4.1
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '5.4'
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 5.4.1
101
+ - !ruby/object:Gem::Dependency
102
+ name: minitest-implicit-subject
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: '1.4'
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 1.4.0
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.4'
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: 1.4.0
121
+ - !ruby/object:Gem::Dependency
122
+ name: rb-readline
123
+ requirement: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - "~>"
126
+ - !ruby/object:Gem::Version
127
+ version: '0.5'
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 0.5.0
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.5'
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: 0.5.0
141
+ - !ruby/object:Gem::Dependency
142
+ name: guard
143
+ requirement: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - "~>"
146
+ - !ruby/object:Gem::Version
147
+ version: '2.11'
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: 2.11.1
151
+ type: :development
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - "~>"
156
+ - !ruby/object:Gem::Version
157
+ version: '2.11'
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: 2.11.1
161
+ - !ruby/object:Gem::Dependency
162
+ name: guard-minitest
163
+ requirement: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - "~>"
166
+ - !ruby/object:Gem::Version
167
+ version: '2.3'
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: 2.3.2
171
+ type: :development
172
+ prerelease: false
173
+ version_requirements: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - "~>"
176
+ - !ruby/object:Gem::Version
177
+ version: '2.3'
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: 2.3.2
181
+ - !ruby/object:Gem::Dependency
182
+ name: mysql2
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '0.3'
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: 0.3.18
191
+ type: :development
192
+ prerelease: false
193
+ version_requirements: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - "~>"
196
+ - !ruby/object:Gem::Version
197
+ version: '0.3'
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: 0.3.18
201
+ - !ruby/object:Gem::Dependency
202
+ name: moped
203
+ requirement: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - "~>"
206
+ - !ruby/object:Gem::Version
207
+ version: '2.0'
208
+ - - ">="
209
+ - !ruby/object:Gem::Version
210
+ version: 2.0.3
211
+ type: :development
212
+ prerelease: false
213
+ version_requirements: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - "~>"
216
+ - !ruby/object:Gem::Version
217
+ version: '2.0'
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: 2.0.3
221
+ - !ruby/object:Gem::Dependency
222
+ name: sinatra
223
+ requirement: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - "~>"
226
+ - !ruby/object:Gem::Version
227
+ version: '1.4'
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: 1.4.5
231
+ type: :runtime
232
+ prerelease: false
233
+ version_requirements: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - "~>"
236
+ - !ruby/object:Gem::Version
237
+ version: '1.4'
238
+ - - ">="
239
+ - !ruby/object:Gem::Version
240
+ version: 1.4.5
241
+ - !ruby/object:Gem::Dependency
242
+ name: sinatra-contrib
243
+ requirement: !ruby/object:Gem::Requirement
244
+ requirements:
245
+ - - "~>"
246
+ - !ruby/object:Gem::Version
247
+ version: '1.4'
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: 1.4.2
251
+ type: :runtime
252
+ prerelease: false
253
+ version_requirements: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - "~>"
256
+ - !ruby/object:Gem::Version
257
+ version: '1.4'
258
+ - - ">="
259
+ - !ruby/object:Gem::Version
260
+ version: 1.4.2
261
+ - !ruby/object:Gem::Dependency
262
+ name: rack
263
+ requirement: !ruby/object:Gem::Requirement
264
+ requirements:
265
+ - - "~>"
266
+ - !ruby/object:Gem::Version
267
+ version: '1.6'
268
+ - - ">="
269
+ - !ruby/object:Gem::Version
270
+ version: 1.6.0
271
+ type: :runtime
272
+ prerelease: false
273
+ version_requirements: !ruby/object:Gem::Requirement
274
+ requirements:
275
+ - - "~>"
276
+ - !ruby/object:Gem::Version
277
+ version: '1.6'
278
+ - - ">="
279
+ - !ruby/object:Gem::Version
280
+ version: 1.6.0
281
+ - !ruby/object:Gem::Dependency
282
+ name: thin
283
+ requirement: !ruby/object:Gem::Requirement
284
+ requirements:
285
+ - - "~>"
286
+ - !ruby/object:Gem::Version
287
+ version: '1.6'
288
+ - - ">="
289
+ - !ruby/object:Gem::Version
290
+ version: 1.6.3
291
+ type: :runtime
292
+ prerelease: false
293
+ version_requirements: !ruby/object:Gem::Requirement
294
+ requirements:
295
+ - - "~>"
296
+ - !ruby/object:Gem::Version
297
+ version: '1.6'
298
+ - - ">="
299
+ - !ruby/object:Gem::Version
300
+ version: 1.6.3
301
+ - !ruby/object:Gem::Dependency
302
+ name: thor
303
+ requirement: !ruby/object:Gem::Requirement
304
+ requirements:
305
+ - - "~>"
306
+ - !ruby/object:Gem::Version
307
+ version: '0.19'
308
+ - - ">="
309
+ - !ruby/object:Gem::Version
310
+ version: 0.19.1
311
+ type: :runtime
312
+ prerelease: false
313
+ version_requirements: !ruby/object:Gem::Requirement
314
+ requirements:
315
+ - - "~>"
316
+ - !ruby/object:Gem::Version
317
+ version: '0.19'
318
+ - - ">="
319
+ - !ruby/object:Gem::Version
320
+ version: 0.19.1
321
+ description: Make database queries available via HTTP
322
+ email:
323
+ - jeff.mathiot@gmail.com
324
+ executables:
325
+ - gros_calin
326
+ extensions: []
327
+ extra_rdoc_files: []
328
+ files:
329
+ - ".gitignore"
330
+ - ".travis.yml"
331
+ - Gemfile
332
+ - Guardfile
333
+ - LICENSE.txt
334
+ - README.md
335
+ - Rakefile
336
+ - bin/gros_calin
337
+ - config.ru
338
+ - gros_calin.gemspec
339
+ - lib/gros_calin.rb
340
+ - lib/gros_calin/cli.rb
341
+ - lib/gros_calin/config.rb
342
+ - lib/gros_calin/driver.rb
343
+ - lib/gros_calin/mongo_db.rb
344
+ - lib/gros_calin/mysql.rb
345
+ - lib/gros_calin/version.rb
346
+ - romain-gary.jpg
347
+ - spec/gros_calin/config_spec.rb
348
+ - spec/gros_calin/driver_spec.rb
349
+ - spec/gros_calin/mongodb_spec.rb
350
+ - spec/gros_calin/mysql_spec.rb
351
+ - spec/gros_calin_spec.rb
352
+ - spec/spec_helper.rb
353
+ homepage: https://github.com/servebox/gros_calin
354
+ licenses:
355
+ - MIT
356
+ metadata: {}
357
+ post_install_message:
358
+ rdoc_options: []
359
+ require_paths:
360
+ - lib
361
+ required_ruby_version: !ruby/object:Gem::Requirement
362
+ requirements:
363
+ - - ">="
364
+ - !ruby/object:Gem::Version
365
+ version: '0'
366
+ required_rubygems_version: !ruby/object:Gem::Requirement
367
+ requirements:
368
+ - - ">="
369
+ - !ruby/object:Gem::Version
370
+ version: '0'
371
+ requirements: []
372
+ rubyforge_project:
373
+ rubygems_version: 2.2.2
374
+ signing_key:
375
+ specification_version: 4
376
+ summary: Make database queries available via HTTP
377
+ test_files:
378
+ - spec/gros_calin/config_spec.rb
379
+ - spec/gros_calin/driver_spec.rb
380
+ - spec/gros_calin/mongodb_spec.rb
381
+ - spec/gros_calin/mysql_spec.rb
382
+ - spec/gros_calin_spec.rb
383
+ - spec/spec_helper.rb