grapeape 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d68ce46191052f09bdbbf499ebae1fd5bb9e48d9
4
+ data.tar.gz: 38e7d9103f7f8c389c7db49bbf1653cc6de502f6
5
+ SHA512:
6
+ metadata.gz: edb0b9d3ac677a0145999073dee62348ecd80a21692ad64a4949609d2fa0018a186384983b1bbcefd1fc996d8a5b6c77f0a49ea717433393e628f0ea5daad6b3
7
+ data.tar.gz: 555485bfe9218645b428328ecd0d3eeab1806683ecb8192b8cd1e4a99e4af11ed6f10536bc0212e83cd0085a342b5ce3ae1885f79e1bc8b93def986cb0b54da2
@@ -0,0 +1,26 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ lib/bundler/man
9
+ pkg
10
+ rdoc
11
+ spec/reports
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+
16
+ # YARD artifacts
17
+ .yardoc
18
+ _yardoc
19
+ doc/
20
+
21
+ # Rubymine artifacts
22
+ .idea/
23
+
24
+ # RVM
25
+ .ruby-version
26
+ .ruby-gemset
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 2.0.0
3
+ services:
4
+ - rabbitmq
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ ruby '2.0.0'
3
+
4
+ # Specify your gem's dependencies in grapeape.gemspec
5
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 David Justice
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,38 @@
1
+ # GrapeApe
2
+
3
+ [![Code Climate](https://codeclimate.com/github/devigned/grapeape.png)](https://codeclimate.com/github/devigned/grapeape)
4
+ [![Build Status](https://travis-ci.org/devigned/grapeape.png)](https://travis-ci.org/devigned/grapeape)
5
+ [![Coverage Status](https://coveralls.io/repos/devigned/grapeape/badge.png?branch=master)](https://coveralls.io/r/devigned/grapeape?branch=master)
6
+
7
+ Eventmachine driven distributed web API in ruby. The project stands up an event driven web API
8
+ ([goliath](http://postrank-labs.github.io/goliath/) / [grape](http://intridea.github.io/grape/)) backed by
9
+ [AMQP](http://www.amqp.org/). AMQP is used to route messages from the web to a collection of event driven worker
10
+ processes in an RPC flow.
11
+
12
+ Note: this is not ready for primetime
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'grapeape'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install grapeape
27
+
28
+ ## Usage
29
+
30
+ TODO: Write usage instructions here
31
+
32
+ ## Contributing
33
+
34
+ 1. Fork it
35
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
36
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
37
+ 4. Push to the branch (`git push origin my-new-feature`)
38
+ 5. Create new Pull Request
@@ -0,0 +1,19 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rubygems'
3
+ require 'rake'
4
+
5
+ def safe_load
6
+ begin
7
+ yield
8
+ rescue LoadError => ex
9
+ puts 'Error loading rake tasks, but will continue...'
10
+ puts ex.message
11
+ end
12
+ end
13
+
14
+ safe_load do
15
+ require 'rspec/core/rake_task'
16
+ RSpec::Core::RakeTask.new(:spec)
17
+ end
18
+
19
+ task :default => [:spec]
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ ruby '2.0.0'
3
+
4
+ gem 'grapeape', path: '../../'
@@ -0,0 +1,2 @@
1
+ web: bundle exec ruby api.rb -sv
2
+ worker: bundle exec ruby worker.rb
@@ -0,0 +1,7 @@
1
+ require 'grape_ape'
2
+
3
+ class Api < GrapeApe::API
4
+ format :json
5
+
6
+ get '/', routing_key: 'foo', method: 'hello_world'
7
+ end
@@ -0,0 +1,9 @@
1
+ require 'grape_ape'
2
+
3
+ class Worker < GrapeApe::Worker
4
+ routing_key :foo
5
+
6
+ def hello_world(request)
7
+ 'Hello World!!'
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'grape_ape/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.platform = Gem::Platform::RUBY
8
+ s.name = 'grapeape'
9
+ s.version = GrapeApe::VERSION
10
+ s.authors = ['David Justice']
11
+ s.email = %w(david@devigned.com)
12
+ s.description = 'Message based, event driven web dsl in ruby'
13
+ s.summary = <<-MSG
14
+ Message based, event driven web dsl in ruby. The project stands up an event driven web (goliath / grape) dsl backed by
15
+ AMQP. AMQP is used to route messages from the web to a collection of event driven worker processes in an RPC flow.
16
+
17
+ This could be used to set up a quick [CQRS](http://martinfowler.com/bliki/CQRS.html) architecture.
18
+ MSG
19
+ s.homepage = ''
20
+ s.license = 'MIT'
21
+ s.required_ruby_version = '>= 2.0.0'
22
+
23
+ s.files = `git ls-files`.split($/)
24
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
25
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
26
+ s.require_paths = %w(lib)
27
+
28
+ s.add_development_dependency 'bundler', '~> 1.3'
29
+ s.add_development_dependency 'rake'
30
+ s.add_development_dependency 'rspec'
31
+ s.add_development_dependency 'rack-test'
32
+ s.add_development_dependency 'em-http-request'
33
+ s.add_development_dependency 'coveralls'
34
+
35
+ s.add_dependency 'goliath'
36
+ s.add_dependency 'grape'
37
+ s.add_dependency 'amqp'
38
+ s.add_dependency 'activesupport'
39
+ end
@@ -0,0 +1,15 @@
1
+ require 'hashie'
2
+ require 'active_support/json'
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+ require 'grape'
5
+ require 'grape_ape/version'
6
+ require 'amqp'
7
+
8
+ module GrapeApe
9
+ autoload :API, 'grape_ape/api'
10
+ autoload :Dispatcher, 'grape_ape/dispatcher'
11
+ autoload :Endpoint, 'grape_ape/endpoint'
12
+ autoload :Worker, 'grape_ape/worker'
13
+ autoload :Server, 'grape_ape/server'
14
+ end
15
+
@@ -0,0 +1,48 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'grape_ape/goliath/application_patch'
3
+
4
+ module GrapeApe
5
+ class API < Grape::API
6
+ REQUIRED_ROUTE_KEYS = [:routing_key, :method]
7
+
8
+ cattr_accessor :app_class
9
+
10
+ class << self
11
+ def inherited(subclass)
12
+ super
13
+ GrapeApe::API.app_class = subclass.name if defined?(GrapeApe::API)
14
+ end
15
+
16
+ def route(methods, paths = %w(/), route_options = {}, &block)
17
+ if ape_route?(route_options)
18
+ endpoint_options = {
19
+ :method => methods,
20
+ :path => paths,
21
+ :route_options => (@namespace_description || {}).deep_merge(@last_description || {}).deep_merge(route_options || {})
22
+ }
23
+
24
+ endpoints << GrapeApe::Endpoint.new(settings.clone, endpoint_options) do
25
+ message = rpc(env, route_options[:routing_key], {method: route_options[:method], params: params})
26
+ if block
27
+ block.call(message)
28
+ else
29
+ message
30
+ end
31
+ end
32
+
33
+ @last_description = nil
34
+ reset_validations!
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def ape_route?(route_options)
43
+ keys = Set.new(route_options.keys)
44
+ Set[*REQUIRED_ROUTE_KEYS].subset?(keys)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,66 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'eventmachine'
3
+ require 'em-synchrony'
4
+ require 'amqp'
5
+
6
+ module GrapeApe
7
+ module Consumer
8
+ module Application
9
+
10
+ module_function
11
+
12
+ def amqp_channel
13
+ @channel
14
+ end
15
+
16
+ def register(routing_key, klass)
17
+ @keys_and_klasses ||= {}
18
+ @keys_and_klasses[routing_key] = klass
19
+ end
20
+
21
+ def run!
22
+ start do
23
+ connect
24
+ end
25
+ end
26
+
27
+ def connect
28
+ AMQP.connect do |connection|
29
+ @channel = AMQP::Channel.new(connection)
30
+
31
+ @keys_and_klasses.each do |key, value|
32
+ q = @channel.queue(key, :auto_delete => true)
33
+ q.subscribe do |metadata, payload|
34
+ value.new.handle_message(metadata, payload)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # Stops the consumer running.
41
+ def stop
42
+ logger.info('Stopping the consumers...')
43
+ EM.stop
44
+ end
45
+
46
+ def start
47
+ EM.epoll
48
+ EM.synchrony do
49
+ trap('INT') { stop }
50
+ trap('TERM') { stop }
51
+ yield if block_given?
52
+ end
53
+ end
54
+
55
+ at_exit do
56
+ # Only run the application if ...
57
+ # - we want it to run
58
+ # - there has been no exception raised
59
+ # - the file that has been run, is the goliath application file
60
+ if $!.nil?
61
+ Application.run!
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ module GrapeApe
2
+ module Dispatcher
3
+ def rpc(env, queue_name, message = {})
4
+ f = Fiber.current
5
+ response = nil
6
+ env['subscription'] = env.grape_amqp_em_channel.subscribe do |msg|
7
+ if msg[:meta].correlation_id == env['correlation_id']
8
+ response = msg[:data]
9
+ f.resume
10
+ end
11
+ end
12
+
13
+ env['correlation_id'] = SecureRandom.uuid
14
+ env.grape_amqp_exchange.publish(message.to_json,
15
+ routing_key: queue_name,
16
+ reply_to: env.grape_amqp_response_queue,
17
+ correlation_id: env['correlation_id'])
18
+ Fiber.yield
19
+ JSON.parse(response)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ module GrapeApe
2
+ class Endpoint < Grape::Endpoint
3
+ include GrapeApe::Dispatcher
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ require 'goliath'
2
+ require 'goliath/websocket'
3
+ require 'grape_ape'
4
+ require 'grape_ape/goliath/server'
5
+ require 'grape_ape/goliath/runner'
6
+
7
+ Goliath::Application.module_eval do
8
+
9
+ Goliath::Application::CALLERS_TO_IGNORE << /\/grape_ape\/goliath_runner.rb$/
10
+ Goliath::Application::CALLERS_TO_IGNORE << /\/ruby-debug-ide-(.+)\//
11
+ Goliath::Application::CALLERS_TO_IGNORE << /\/debase\//
12
+
13
+ module_function
14
+
15
+ def camel_case(str)
16
+ return str if str !~ /_/ && str =~ /[A-Z]+.*/
17
+
18
+ str.split('_').map { |e| e.capitalize }.join
19
+ end
20
+
21
+ alias :super_run! :run!
22
+
23
+ # Execute the application
24
+ #
25
+ # @return [Nil]
26
+ def run!
27
+ if GrapeApe::API.app_class
28
+ begin
29
+ klass = Kernel
30
+ GrapeApe::API.app_class.split('::').each { |con| klass = klass.const_get(con) }
31
+ api = GrapeApe::Server.new(api: klass)
32
+ rescue NameError
33
+ raise NameError, "Class #{@app_class} not found."
34
+ end
35
+
36
+ runner = GrapeApe::Goliath::Runner.new(ARGV, api)
37
+ runner.app = Goliath::Rack::Builder.build(GrapeApe::Server, api)
38
+
39
+ runner.load_plugins(GrapeApe::Server.plugins)
40
+
41
+ runner.run
42
+ else
43
+ super_run!
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,24 @@
1
+ require 'goliath/runner'
2
+ require 'grape_ape/goliath/server'
3
+
4
+ module GrapeApe
5
+ module Goliath
6
+ class Runner < ::Goliath::Runner
7
+
8
+ # Sets up the Goliath server
9
+ #
10
+ # @param log [Logger] The logger to configure the server to log to
11
+ # @return [Server] an instance of a Goliath server
12
+ def setup_server(log = setup_logger)
13
+ server = GrapeApe::Goliath::Server.new(@address, @port)
14
+ server.logger = log
15
+ server.app = @app
16
+ server.api = @api
17
+ server.plugins = @plugins || []
18
+ server.options = @server_options
19
+ server
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,67 @@
1
+ require 'eventmachine'
2
+
3
+ module GrapeApe
4
+ module Goliath
5
+ class Server < ::Goliath::Server
6
+ def start(&blk)
7
+ EM.epoll
8
+ EM.synchrony do
9
+ trap('INT') { stop }
10
+ trap('TERM') { stop }
11
+
12
+ if RUBY_PLATFORM !~ /mswin|mingw/
13
+ trap('HUP') { load_config(options[:config]) }
14
+ end
15
+
16
+ load_config(options[:config])
17
+ load_plugins
18
+ setup_amqp
19
+
20
+ EM.set_effective_user(options[:user]) if options[:user]
21
+
22
+ config[::Goliath::Constants::GOLIATH_SIGNATURE] = EM.start_server(address, port, ::Goliath::Connection) do |conn|
23
+ if options[:ssl]
24
+ conn.start_tls(
25
+ :private_key_file => options[:ssl_key],
26
+ :cert_chain_file => options[:ssl_cert],
27
+ :verify_peer => options[:ssl_verify]
28
+ )
29
+ end
30
+
31
+ conn.port = port
32
+ conn.app = app
33
+ conn.api = api
34
+ conn.logger = logger
35
+ conn.status = status
36
+ conn.config = config
37
+ conn.options = options
38
+ end
39
+
40
+ blk.call(self) if blk
41
+ end
42
+ end
43
+
44
+ def stop
45
+ logger.info('Stopping server...')
46
+ config['grape_amqp_conn'].close { EM.stop }
47
+ end
48
+
49
+ def setup_amqp
50
+ config['grape_amqp_conn'] = AMQP.connect(on_possible_authentication_failure: Proc.new { |settings|
51
+ logger.info "Authentication failed, as expected, settings are: #{settings.inspect}"
52
+ EM.stop
53
+ })
54
+ config['grape_amqp_channel'] = AMQP::Channel.new(config['grape_amqp_conn'])
55
+ config['grape_amqp_exchange'] = config['grape_amqp_channel'].default_exchange
56
+ config['grape_amqp_response_queue'] = SecureRandom.uuid
57
+ config['grape_amqp_em_channel'] = EM::Channel.new
58
+
59
+ q = config['grape_amqp_channel'].queue(config['grape_amqp_response_queue'], exclusive: true)
60
+
61
+ q.subscribe do |meta, payload|
62
+ config['grape_amqp_em_channel'].push({meta: meta, data: payload})
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,15 @@
1
+ require 'goliath'
2
+ require 'goliath/api'
3
+ require 'goliath/websocket'
4
+
5
+ module GrapeApe
6
+ class Server < ::Goliath::API
7
+ def initialize(opts = {})
8
+ @api = opts.delete(:api)
9
+ end
10
+
11
+ def response(env)
12
+ @api.call(env)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ require 'goliath/test_helper'
2
+
3
+ module GrapeApe
4
+ module TestHelper
5
+ include ::Goliath::TestHelper
6
+
7
+ # Wrapper for launching API and executing given code block. This
8
+ # will start the EventMachine reactor running.
9
+ #
10
+ # @param api [Class] The GrapeApe::API class to launch
11
+ # @param options [Hash] The options to pass to the server
12
+ # @param blk [Proc] The code to execute after the server is launched.
13
+ # @note This will not return until stop is called.
14
+ def with_api(api, options = {}, &blk)
15
+ server(GrapeApe::Server.new(api: api), options.delete(:port) || 9900, options, &blk)
16
+ end
17
+
18
+ # Launches an instance of a given API server. The server
19
+ # will launch on the specified port.
20
+ #
21
+ # @param api [Class] The API class to launch
22
+ # @param port [Integer] The port to run the server on
23
+ # @param options [Hash] The options hash to provide to the server
24
+ # @return [Goliath::Server] The executed server
25
+ def server(api, port, options = {}, &blk)
26
+ op = OptionParser.new
27
+
28
+ s = GrapeApe::Goliath::Server.new
29
+ s.logger = setup_logger(options)
30
+ s.api = api
31
+ s.app = ::Goliath::Rack::Builder.build(api.class, s.api)
32
+ s.api.options_parser(op, options)
33
+ s.options = options
34
+ s.port = port
35
+ s.plugins = api.class.plugins
36
+ @test_server_port = s.port if blk
37
+ s.start(&blk)
38
+ s
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module GrapeApe
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,39 @@
1
+ require 'grape_ape/consumer/application'
2
+
3
+ module GrapeApe
4
+ class Worker
5
+ class << self
6
+
7
+ attr_reader :routing_key
8
+
9
+ def routing_key(key)
10
+ @routing_key = key
11
+ Consumer::Application.register(key, self)
12
+ end
13
+ end
14
+
15
+ def handle_message(metadata, payload)
16
+ payload = JSON.parse(payload).with_indifferent_access
17
+ response = if respond_to?(payload[:method].to_sym)
18
+ begin
19
+ ok(send(payload[:method].to_sym, payload[:params]))
20
+ rescue Exception => ex
21
+ error({message: ex.message, backtrace: ex.backtrace})
22
+ end
23
+ else
24
+ error("Worker listening to routing_key: #{self.routing_key} does not have method: #{payload[:method]}")
25
+ end
26
+ Consumer::Application.amqp_channel.default_exchange.publish(response.to_json,
27
+ routing_key: metadata.reply_to,
28
+ correlation_id: metadata.correlation_id)
29
+ end
30
+
31
+ def ok(response)
32
+ [:ok, response]
33
+ end
34
+
35
+ def error(response)
36
+ [:error, response]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe GrapeApe::API do
4
+
5
+ before(:each) do
6
+ described_class.endpoints.clear
7
+ end
8
+
9
+ subject { Class.new(GrapeApe::API) }
10
+
11
+ def app;
12
+ subject
13
+ end
14
+
15
+ describe '.route' do
16
+ let(:ape_options) { {routing_key: 'blah', method: 'hello'} }
17
+
18
+ it 'should ask if the route is an ape route' do
19
+ described_class.should_receive(:ape_route?).with(hash_including(ape_options)).and_return(true)
20
+ described_class.route(:get, '/', ape_options)
21
+ end
22
+
23
+ it 'should call super route with no ape route options' do
24
+ described_class.superclass.should_receive(:route)
25
+ described_class.route(:get, '/')
26
+ end
27
+
28
+ it 'should add an endpoint to the endpoint collection' do
29
+ expect {
30
+ described_class.route(:get, '/', ape_options)
31
+ }.to change { described_class.endpoints.count }.from(0).to(1)
32
+ end
33
+
34
+ context 'api responses' do
35
+ it 'returns the message that rpc returns with out a block provided to the dsl method' do
36
+ subject.get '/', ape_options
37
+ GrapeApe::Endpoint.any_instance.should_receive(:rpc).and_return('hello_world')
38
+ get '/'
39
+ last_response.body.should eql 'hello_world'
40
+ end
41
+
42
+ it 'should pass the rpc message to the provided block for processing' do
43
+ subject.get '/', ape_options do |message|
44
+ 'hello ' + message
45
+ end
46
+ GrapeApe::Endpoint.any_instance.should_receive(:rpc).and_return('world')
47
+ get '/'
48
+ last_response.body.should eql 'hello world'
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe GrapeApe::Dispatcher do
4
+
5
+ end
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support'))
4
+
5
+ require 'coveralls'
6
+ Coveralls.wear!
7
+
8
+ require 'grape_ape'
9
+ require 'rubygems'
10
+ require 'bundler'
11
+ require 'rack/test'
12
+ Bundler.setup :default, :test
13
+
14
+ Dir["#{File.dirname(__FILE__)}/support/*.rb"].each do |file|
15
+ require file
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.order = 'random'
20
+ config.include Rack::Test::Methods
21
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require 'grape_ape/test_helper'
3
+
4
+ describe GrapeApe::TestHelper do
5
+ include GrapeApe::TestHelper
6
+
7
+ class TestApi < GrapeApe::API
8
+ format :json
9
+
10
+ get '/' do
11
+ {hello: 'world'}
12
+ end
13
+ end
14
+
15
+ context 'make request to root' do
16
+ it 'should get a 200' do
17
+ with_api(TestApi) do
18
+ em_http_client = get_request path: '/'
19
+ em_http_client.response_header.status.should eq(200)
20
+ end
21
+ end
22
+
23
+ it 'should get json' do
24
+ with_api(TestApi) do
25
+ em_http_client = get_request path: '/'
26
+ JSON.parse(em_http_client.response).should eq({'hello' => 'world'})
27
+ end
28
+ end
29
+ end
30
+
31
+ context 'make request to not routed path' do
32
+ it 'get a 404' do
33
+ with_api(TestApi) do
34
+ em_http_client = get_request path: '/something'
35
+ em_http_client.response_header.status.should eq(404)
36
+ end
37
+ end
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,219 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grapeape
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - David Justice
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-05 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.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack-test
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: em-http-request
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: coveralls
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: goliath
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: grape
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: amqp
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: activesupport
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: Message based, event driven web dsl in ruby
154
+ email:
155
+ - david@devigned.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - .gitignore
161
+ - .travis.yml
162
+ - Gemfile
163
+ - LICENSE
164
+ - README.md
165
+ - Rakefile
166
+ - examples/hello_world/Gemfile
167
+ - examples/hello_world/Procfile
168
+ - examples/hello_world/api.rb
169
+ - examples/hello_world/worker.rb
170
+ - grape_ape.gemspec
171
+ - lib/grape_ape.rb
172
+ - lib/grape_ape/api.rb
173
+ - lib/grape_ape/consumer/application.rb
174
+ - lib/grape_ape/dispatcher.rb
175
+ - lib/grape_ape/endpoint.rb
176
+ - lib/grape_ape/goliath/application_patch.rb
177
+ - lib/grape_ape/goliath/runner.rb
178
+ - lib/grape_ape/goliath/server.rb
179
+ - lib/grape_ape/server.rb
180
+ - lib/grape_ape/test_helper.rb
181
+ - lib/grape_ape/version.rb
182
+ - lib/grape_ape/worker.rb
183
+ - spec/api_spec.rb
184
+ - spec/dispatcher_spec.rb
185
+ - spec/spec_helper.rb
186
+ - spec/test_helper_spec.rb
187
+ homepage: ''
188
+ licenses:
189
+ - MIT
190
+ metadata: {}
191
+ post_install_message:
192
+ rdoc_options: []
193
+ require_paths:
194
+ - lib
195
+ required_ruby_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - '>='
198
+ - !ruby/object:Gem::Version
199
+ version: 2.0.0
200
+ required_rubygems_version: !ruby/object:Gem::Requirement
201
+ requirements:
202
+ - - '>='
203
+ - !ruby/object:Gem::Version
204
+ version: '0'
205
+ requirements: []
206
+ rubyforge_project:
207
+ rubygems_version: 2.0.3
208
+ signing_key:
209
+ specification_version: 4
210
+ summary: Message based, event driven web dsl in ruby. The project stands up an event
211
+ driven web (goliath / grape) dsl backed by AMQP. AMQP is used to route messages
212
+ from the web to a collection of event driven worker processes in an RPC flow. This
213
+ could be used to set up a quick [CQRS](http://martinfowler.com/bliki/CQRS.html)
214
+ architecture.
215
+ test_files:
216
+ - spec/api_spec.rb
217
+ - spec/dispatcher_spec.rb
218
+ - spec/spec_helper.rb
219
+ - spec/test_helper_spec.rb