vodka 0.1.0 → 0.1.2
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/.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 [](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
|