vodka 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -0
- data/README.md +33 -10
- data/lib/party.rb +2 -1
- data/lib/vodka/client.rb +0 -2
- data/lib/vodka/client/middleware/signed_request.rb +1 -1
- data/lib/vodka/client/middleware/signed_response.rb +1 -1
- data/lib/vodka/configuration.rb +27 -12
- data/lib/vodka/server.rb +3 -0
- data/lib/vodka/server/controllers/vodka_controller.rb +1 -5
- data/lib/vodka/server/handlers/resource.rb +6 -2
- data/lib/vodka/server/handlers/response.rb +10 -6
- data/lib/vodka/server/handlers/scaffold.rb +5 -35
- data/lib/vodka/server/middleware/signed_request.rb +6 -4
- data/lib/vodka/server/plugins/presentable.rb +24 -9
- data/lib/vodka/server/presenters/vodka_presenter.rb +23 -0
- data/lib/vodka/server/railtie.rb +13 -0
- data/lib/vodka/server/relation.rb +95 -0
- data/lib/vodka/server/response.rb +18 -7
- data/lib/vodka/version.rb +1 -1
- data/script/dummy_console +6 -0
- data/script/setup_db +2 -2
- data/script/show_logs +1 -1
- data/script/spec +5 -9
- data/script/start_server +2 -2
- data/script/stop_server +1 -1
- data/spec/client/article.rb +2 -0
- data/spec/client/author.rb +3 -0
- data/spec/client/comment.rb +4 -0
- data/spec/dummy/Gemfile +6 -5
- data/spec/dummy/Gemfile.lock +19 -4
- data/spec/dummy/app/controllers/articles_controller.rb +5 -0
- data/spec/dummy/app/controllers/vodka/authors_controller.rb +4 -0
- data/spec/dummy/app/controllers/vodka/comments_controller.rb +4 -0
- data/spec/dummy/app/models/article.rb +9 -2
- data/spec/dummy/app/models/author.rb +5 -0
- data/spec/dummy/app/models/comment.rb +12 -0
- data/spec/dummy/app/models/like.rb +9 -0
- data/spec/dummy/app/presenters/article_presenter.rb +9 -0
- data/spec/dummy/app/presenters/author_custom_presenter.rb +5 -0
- data/spec/dummy/app/presenters/comment_presenter.rb +13 -0
- data/spec/dummy/config/database.yml +3 -3
- data/spec/dummy/config/initializers/mongo_mapper_connection.rb +4 -0
- data/spec/dummy/config/initializers/vodka_setup.rb +1 -0
- data/spec/dummy/config/mongoid.yml +32 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/thin.yml +1 -1
- data/spec/dummy/db/migrate/20130217185353_create_articles.rb +1 -0
- data/spec/dummy/db/schema.rb +1 -0
- data/spec/dummy/db/seeds.rb +18 -1
- data/spec/her/extensions/paginated/paginate_spec.rb +32 -0
- data/spec/middleware/response_locale_spec.rb +2 -0
- data/spec/requests/relations_spec.rb +9 -0
- data/spec/spec_helper.rb +2 -1
- data/vodka.gemspec +1 -1
- metadata +23 -8
- data/lib/vodka/her/extensions/extended_orm.rb +0 -92
- data/lib/vodka/her/extensions/will_paginate.rb +0 -32
- data/spec/her/extensions/will_paginate/paginate_spec.rb +0 -16
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
-
# Vodka
|
1
|
+
# Vodka [![Build Status](https://travis-ci.org/magnolia-fan/vodka.png)](https://travis-ci.org/magnolia-fan/vodka)
|
2
2
|
Vodka makes communication easier. Always.
|
3
3
|
|
4
4
|
Vodka uses [Her](https://github.com/remiprev/her) as a REST client.
|
5
5
|
|
6
|
-
|
6
|
+
Vodka currently supports these ORMs on server:
|
7
7
|
- [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord)
|
8
|
+
- [Mongoid](https://github.com/mongoid/mongoid)
|
8
9
|
- [MongoMapper](https://github.com/jnunemaker/mongomapper)
|
9
10
|
|
10
|
-
And the following plugins:
|
11
|
-
- [WillPaginate](https://github.com/mislav/will_paginate)
|
11
|
+
And the following plugins on client:
|
12
|
+
- [WillPaginate](https://github.com/mislav/will_paginate) (and compatible)
|
12
13
|
|
13
14
|
Vodka supports I18n. If you change locale on client you may expect getting response from server in this locale.
|
14
15
|
|
@@ -31,6 +32,11 @@ Add initializer `vodka_setup.rb` to `config/initializers`:
|
|
31
32
|
Vodka::Server.configure do |c|
|
32
33
|
c.request_secret = '8089c2189321798bf60df6b8c01bb661fb585080'
|
33
34
|
c.response_secret = '3ecb42ac23cd58994a6518971e7e31d2f4545e3c'
|
35
|
+
|
36
|
+
# Optional
|
37
|
+
|
38
|
+
# Any class that responds to .hexdigest method
|
39
|
+
c.digest = Digest::SHA1 # Default is Digest::SHA512
|
34
40
|
end
|
35
41
|
```
|
36
42
|
|
@@ -72,8 +78,8 @@ class Article < ActiveRecord::Base
|
|
72
78
|
scope :best, ->{ where('rating > 100') }
|
73
79
|
validates_presence_of :title, :body
|
74
80
|
|
75
|
-
#
|
76
|
-
present_with
|
81
|
+
# If .present_with was not called, default presenter (named as ModelNamePresenter) will be used
|
82
|
+
# present_with ArticlePresenter
|
77
83
|
end
|
78
84
|
|
79
85
|
# comment.rb
|
@@ -82,8 +88,8 @@ class Comment < ActiveRecord::Base
|
|
82
88
|
belongs_to :user
|
83
89
|
validates_presence_of :body
|
84
90
|
|
85
|
-
# Defines
|
86
|
-
present_with
|
91
|
+
# Defines custom presenter
|
92
|
+
present_with CommentCustomPresenter
|
87
93
|
|
88
94
|
def approve
|
89
95
|
update_attributes(status: 'approved')
|
@@ -95,6 +101,16 @@ class Comment < ActiveRecord::Base
|
|
95
101
|
end
|
96
102
|
```
|
97
103
|
|
104
|
+
Add presenters:
|
105
|
+
```ruby
|
106
|
+
# article_presenter.rb
|
107
|
+
class ArticlePresenter < VodkaPresenter
|
108
|
+
def present
|
109
|
+
json(id: id, title: title)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
98
114
|
## Configuring client
|
99
115
|
Add initializer `vodka_setup.rb` to `config/initializers`:
|
100
116
|
|
@@ -103,10 +119,17 @@ Vodka::Client.configure do |c|
|
|
103
119
|
c.api_url = 'https://api.myproject.org/vodka'
|
104
120
|
c.request_secret = '8089c2189321798bf60df6b8c01bb661fb585080' # Same as server's
|
105
121
|
c.response_secret = '3ecb42ac23cd58994a6518971e7e31d2f4545e3c' # Same as server's
|
122
|
+
|
123
|
+
# Optional
|
124
|
+
|
125
|
+
# Any class that responds to .hexdigest method
|
126
|
+
c.digest = Digest::SHA1 # Default is Digest::SHA512, same as server's
|
127
|
+
|
128
|
+
# Configure Her automatically
|
129
|
+
c.auto_configure_her = false
|
106
130
|
end
|
107
|
-
Vodka::Client.configure_her!
|
108
131
|
|
109
|
-
#
|
132
|
+
# You can call Vodka::Client.config.configure_her! or configure Her yourself
|
110
133
|
# Her::API.setup(url: Vodka::Client.config.api_url) do |c|
|
111
134
|
# c.use Vodka::Client::Middleware::ErrorAware
|
112
135
|
# c.use Vodka::Client::Middleware::SignedRequest
|
data/lib/party.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'i18n'
|
2
2
|
require 'vodka/client'
|
3
3
|
require './spec/client/article'
|
4
|
+
require './spec/client/author'
|
5
|
+
require './spec/client/comment'
|
4
6
|
|
5
7
|
Vodka::Client.configure do |c|
|
6
8
|
c.api_url = 'http://0.0.0.0:3000/vodka'
|
7
9
|
c.request_secret = '8089c2189321798bf60df6b8c01bb661fb585080'
|
8
10
|
c.response_secret = '3ecb42ac23cd58994a6518971e7e31d2f4545e3c'
|
9
11
|
end
|
10
|
-
Vodka::Client.configure_her!
|
data/lib/vodka/client.rb
CHANGED
@@ -8,8 +8,6 @@ require 'vodka/client/exceptions/security_exception'
|
|
8
8
|
require 'vodka/client/middleware/signed_request'
|
9
9
|
require 'vodka/client/middleware/signed_response'
|
10
10
|
require 'vodka/client/middleware/error_aware'
|
11
|
-
require 'vodka/her/extensions/extended_orm'
|
12
|
-
require 'vodka/her/extensions/will_paginate'
|
13
11
|
|
14
12
|
module Vodka
|
15
13
|
module Client
|
@@ -31,7 +31,7 @@ module Vodka
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def expected_response_signature
|
34
|
-
|
34
|
+
Vodka::Client.config.digest.hexdigest([request_id, Vodka::Client.config.response_secret].join)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
data/lib/vodka/configuration.rb
CHANGED
@@ -1,6 +1,31 @@
|
|
1
1
|
module Vodka
|
2
2
|
class Configuration
|
3
|
-
attr_accessor :request_secret,
|
3
|
+
attr_accessor :request_secret, # Secret token to use in signing requests, stronger is better
|
4
|
+
:response_secret, # Secret token to use in signing responses, stronger is better
|
5
|
+
:digest, # Digest algorithm to use in signing process
|
6
|
+
:perform_request_signing, # If the value is set to false no signing middlewares will be injected
|
7
|
+
:her_auto_configure, # Configure Her automatically
|
8
|
+
:api_url, # REST API endpoint passed to Her
|
9
|
+
:prefix # Almost the same thing, path prefix for server
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@digest = Digest::SHA512
|
13
|
+
@perform_request_signing = true
|
14
|
+
@her_auto_configure = true
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure_her!
|
18
|
+
raise Exception.new('api_url must be set') if api_url.nil?
|
19
|
+
|
20
|
+
::Her::API.setup(url: api_url) do |c|
|
21
|
+
c.use(Vodka::Client::Middleware::ErrorAware)
|
22
|
+
c.use(Vodka::Client::Middleware::SignedRequest) if perform_request_signing
|
23
|
+
c.use(Faraday::Request::UrlEncoded)
|
24
|
+
c.use(Vodka::Client::Middleware::SignedResponse) if perform_request_signing
|
25
|
+
c.use(::Her::Middleware::SecondLevelParseJSON)
|
26
|
+
c.use(Faraday::Adapter::NetHttp)
|
27
|
+
end
|
28
|
+
end
|
4
29
|
end
|
5
30
|
|
6
31
|
module Configurable
|
@@ -10,17 +35,7 @@ module Vodka
|
|
10
35
|
|
11
36
|
def configure
|
12
37
|
yield config if block_given?
|
13
|
-
|
14
|
-
|
15
|
-
def configure_her!
|
16
|
-
::Her::API.setup(url: Vodka::Client.config.api_url) do |c|
|
17
|
-
c.use Vodka::Client::Middleware::ErrorAware
|
18
|
-
c.use Vodka::Client::Middleware::SignedRequest
|
19
|
-
c.use Faraday::Request::UrlEncoded
|
20
|
-
c.use Vodka::Client::Middleware::SignedResponse
|
21
|
-
c.use ::Her::Middleware::SecondLevelParseJSON
|
22
|
-
c.use Faraday::Adapter::NetHttp
|
23
|
-
end
|
38
|
+
config.configure_her! if config.her_auto_configure
|
24
39
|
end
|
25
40
|
end
|
26
41
|
end
|
data/lib/vodka/server.rb
CHANGED
@@ -2,14 +2,17 @@ require 'vodka'
|
|
2
2
|
require 'vodka/server/middleware/signed_request'
|
3
3
|
require 'vodka/server/railtie'
|
4
4
|
require 'vodka/server/response'
|
5
|
+
require 'vodka/server/relation'
|
5
6
|
require 'vodka/server/handlers/resource'
|
6
7
|
require 'vodka/server/handlers/response'
|
7
8
|
require 'vodka/server/handlers/scaffold'
|
8
9
|
require 'vodka/server/controllers/vodka_controller'
|
10
|
+
require 'vodka/server/presenters/vodka_presenter'
|
9
11
|
require 'vodka/server/plugins/presentable'
|
10
12
|
|
11
13
|
module Vodka
|
12
14
|
module Server
|
13
15
|
extend Configurable
|
16
|
+
config.her_auto_configure = false
|
14
17
|
end
|
15
18
|
end
|
@@ -7,7 +7,7 @@ module Vodka
|
|
7
7
|
include Handlers::Resource
|
8
8
|
include Handlers::Response
|
9
9
|
|
10
|
-
before_filter :set_response_id, :
|
10
|
+
before_filter :set_response_id, :set_locale
|
11
11
|
before_filter :handle_not_found, only: [:show, :update, :destroy]
|
12
12
|
|
13
13
|
private
|
@@ -18,10 +18,6 @@ module Vodka
|
|
18
18
|
I18n.locale = locale if locale.present? && locale.to_sym.in?(I18n.available_locales)
|
19
19
|
end
|
20
20
|
|
21
|
-
def fix_params_names
|
22
|
-
params[:password] = params.delete(:buzzword) unless params[:buzzword].nil?
|
23
|
-
end
|
24
|
-
|
25
21
|
def handle_not_found
|
26
22
|
not_found if resource.nil? && vodka_response.success == false
|
27
23
|
end
|
@@ -11,14 +11,18 @@ module Vodka
|
|
11
11
|
def extract_resource
|
12
12
|
return unless params.has_key?(:id)
|
13
13
|
|
14
|
-
new_resource =
|
14
|
+
new_resource = begin
|
15
|
+
resource_class.find(params[:id])
|
16
|
+
rescue
|
17
|
+
nil
|
18
|
+
end
|
15
19
|
vodka_response.success = false if new_resource.nil?
|
16
20
|
|
17
21
|
new_resource
|
18
22
|
end
|
19
23
|
|
20
24
|
def filtered_params
|
21
|
-
params.
|
25
|
+
params.permit(*(resource_class.attribute_names - ['id']).map(&:to_sym))
|
22
26
|
end
|
23
27
|
|
24
28
|
# Resource magick
|
@@ -2,26 +2,30 @@ module Vodka
|
|
2
2
|
module Server
|
3
3
|
module Handlers
|
4
4
|
module Response
|
5
|
-
|
6
5
|
def vodka_response
|
7
6
|
@vodka_response ||= Vodka::Server::Response.new
|
8
7
|
end
|
9
8
|
|
10
9
|
private
|
11
10
|
|
12
|
-
def respond_with_resource(custom_resource = nil)
|
11
|
+
def respond_with_resource(custom_resource = nil, presenter = nil)
|
13
12
|
vodka_response.data = custom_resource || resource
|
13
|
+
vodka_response.presenter = presenter unless presenter.nil?
|
14
14
|
respond!
|
15
15
|
end
|
16
16
|
|
17
|
-
def respond_with_collection(resources)
|
18
|
-
vodka_response.data = resources
|
17
|
+
def respond_with_collection(resources, presenter = nil)
|
18
|
+
vodka_response.data = resources.to_a
|
19
|
+
vodka_response.presenter = presenter unless presenter.nil?
|
19
20
|
respond!
|
20
21
|
end
|
21
22
|
|
22
23
|
def respond!
|
23
|
-
|
24
|
-
|
24
|
+
if Vodka::Server.config.perform_request_signing
|
25
|
+
response.headers['X-Response-Id'] = vodka_response.id
|
26
|
+
response.headers['X-Response-Signature'] = vodka_response.signature
|
27
|
+
end
|
28
|
+
|
25
29
|
return render text: vodka_response.json, status: vodka_response.code, content_type: 'application/json'
|
26
30
|
end
|
27
31
|
|
@@ -3,19 +3,9 @@ module Vodka
|
|
3
3
|
module Handlers
|
4
4
|
module Scaffold
|
5
5
|
def index
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
resources = [resource_class.last]
|
10
|
-
elsif params[:vodka_special_where].present?
|
11
|
-
resources = vodka_special_where
|
12
|
-
elsif params[:page].present? && defined?(::WillPaginate)
|
13
|
-
resources = vodka_special_paginate
|
14
|
-
else
|
15
|
-
resources = resource_class.all
|
16
|
-
end
|
17
|
-
|
18
|
-
respond_with_collection(resources)
|
6
|
+
relation = Vodka::Server::Relation.new(resource_class, params)
|
7
|
+
vodka_response.metadata.merge!(relation.metadata)
|
8
|
+
respond_with_collection(relation.data)
|
19
9
|
end
|
20
10
|
|
21
11
|
def show
|
@@ -32,31 +22,11 @@ module Vodka
|
|
32
22
|
end
|
33
23
|
|
34
24
|
def destroy
|
35
|
-
|
25
|
+
method = params.has_key?(:soft) ? :delete : :destroy
|
26
|
+
resource.send(method)
|
36
27
|
vodka_response.success = resource.destroyed?
|
37
28
|
respond_with_resource
|
38
29
|
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def vodka_special_where
|
43
|
-
relation = resource_class
|
44
|
-
conditions = MultiJson.load(params[:vodka_special_where])
|
45
|
-
conditions.each do |condition|
|
46
|
-
if condition.is_a?(Hash)
|
47
|
-
relation = relation.where(condition)
|
48
|
-
else
|
49
|
-
relation = relation.where(*condition)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
relation.all
|
53
|
-
end
|
54
|
-
|
55
|
-
def vodka_special_paginate
|
56
|
-
data = resource_class.paginate(page: params[:page], per_page: params[:per_page]).all
|
57
|
-
vodka_response.metadata = { page: data.current_page, per_page: data.per_page, total: resource_class.count }
|
58
|
-
data
|
59
|
-
end
|
60
30
|
end
|
61
31
|
end
|
62
32
|
end
|
@@ -2,7 +2,7 @@ module Vodka
|
|
2
2
|
module Server
|
3
3
|
module Middleware
|
4
4
|
class SignedRequest
|
5
|
-
attr_reader :app, :env
|
5
|
+
attr_reader :app, :env
|
6
6
|
|
7
7
|
def initialize(app, options = {})
|
8
8
|
@app, @options = app, options
|
@@ -10,7 +10,9 @@ module Vodka
|
|
10
10
|
|
11
11
|
def call(env)
|
12
12
|
@env = env
|
13
|
-
|
13
|
+
|
14
|
+
return app.call(env) if Vodka::Server.config.perform_request_signing == false
|
15
|
+
return app.call(env) unless env['REQUEST_PATH'].start_with?(Vodka::Server.config.prefix)
|
14
16
|
|
15
17
|
request_signature_valid? ? app.call(env) : forbidden
|
16
18
|
end
|
@@ -28,11 +30,11 @@ module Vodka
|
|
28
30
|
end
|
29
31
|
|
30
32
|
def expected_request_signature
|
31
|
-
|
33
|
+
Vodka::Server.config.digest.hexdigest([request_id, Vodka::Server.config.request_secret].join)
|
32
34
|
end
|
33
35
|
|
34
36
|
def response_signature
|
35
|
-
|
37
|
+
Vodka::Server.config.digest.hexdigest([request_id, Vodka::Server.config.response_secret].join)
|
36
38
|
end
|
37
39
|
|
38
40
|
def forbidden
|
@@ -2,12 +2,20 @@ module Vodka
|
|
2
2
|
module Server
|
3
3
|
module Plugins
|
4
4
|
module Presentable
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def present
|
8
|
+
presenter_class = if self.class.class_variable_defined?(:@@presenter)
|
9
|
+
self.class.class_variable_get(:@@presenter)
|
10
|
+
else
|
11
|
+
Object.const_get(:"#{self.class.name}Presenter")
|
12
|
+
end
|
13
|
+
presenter_class.new(self).present
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def present_with(klass)
|
18
|
+
class_variable_set(:@@presenter, klass)
|
11
19
|
end
|
12
20
|
end
|
13
21
|
end
|
@@ -15,6 +23,13 @@ module Vodka
|
|
15
23
|
end
|
16
24
|
end
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
if defined?(ActiveRecord)
|
27
|
+
ActiveRecord::Base.send(:include, Vodka::Server::Plugins::Presentable)
|
28
|
+
end
|
29
|
+
if defined?(MongoMapper)
|
30
|
+
MongoMapper::Document.send(:include, Vodka::Server::Plugins::Presentable)
|
31
|
+
MongoMapper::EmbeddedDocument.send(:include, Vodka::Server::Plugins::Presentable)
|
32
|
+
end
|
33
|
+
if defined?(Mongoid)
|
34
|
+
Mongoid::Document.send(:include, Vodka::Server::Plugins::Presentable)
|
35
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Vodka
|
2
|
+
module Server
|
3
|
+
class VodkaPresenter
|
4
|
+
attr_accessor :resource
|
5
|
+
|
6
|
+
def initialize(resource)
|
7
|
+
@resource = resource
|
8
|
+
end
|
9
|
+
|
10
|
+
def present
|
11
|
+
raise ::Exception.new("#{self.class.name}#present must be defined!")
|
12
|
+
end
|
13
|
+
|
14
|
+
def json(hash)
|
15
|
+
hash[:errors] = resource.errors.messages
|
16
|
+
hash[:metadata] = {}
|
17
|
+
hash
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
VodkaPresenter = Vodka::Server::VodkaPresenter
|