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.
Files changed (58) hide show
  1. data/.travis.yml +2 -0
  2. data/README.md +33 -10
  3. data/lib/party.rb +2 -1
  4. data/lib/vodka/client.rb +0 -2
  5. data/lib/vodka/client/middleware/signed_request.rb +1 -1
  6. data/lib/vodka/client/middleware/signed_response.rb +1 -1
  7. data/lib/vodka/configuration.rb +27 -12
  8. data/lib/vodka/server.rb +3 -0
  9. data/lib/vodka/server/controllers/vodka_controller.rb +1 -5
  10. data/lib/vodka/server/handlers/resource.rb +6 -2
  11. data/lib/vodka/server/handlers/response.rb +10 -6
  12. data/lib/vodka/server/handlers/scaffold.rb +5 -35
  13. data/lib/vodka/server/middleware/signed_request.rb +6 -4
  14. data/lib/vodka/server/plugins/presentable.rb +24 -9
  15. data/lib/vodka/server/presenters/vodka_presenter.rb +23 -0
  16. data/lib/vodka/server/railtie.rb +13 -0
  17. data/lib/vodka/server/relation.rb +95 -0
  18. data/lib/vodka/server/response.rb +18 -7
  19. data/lib/vodka/version.rb +1 -1
  20. data/script/dummy_console +6 -0
  21. data/script/setup_db +2 -2
  22. data/script/show_logs +1 -1
  23. data/script/spec +5 -9
  24. data/script/start_server +2 -2
  25. data/script/stop_server +1 -1
  26. data/spec/client/article.rb +2 -0
  27. data/spec/client/author.rb +3 -0
  28. data/spec/client/comment.rb +4 -0
  29. data/spec/dummy/Gemfile +6 -5
  30. data/spec/dummy/Gemfile.lock +19 -4
  31. data/spec/dummy/app/controllers/articles_controller.rb +5 -0
  32. data/spec/dummy/app/controllers/vodka/authors_controller.rb +4 -0
  33. data/spec/dummy/app/controllers/vodka/comments_controller.rb +4 -0
  34. data/spec/dummy/app/models/article.rb +9 -2
  35. data/spec/dummy/app/models/author.rb +5 -0
  36. data/spec/dummy/app/models/comment.rb +12 -0
  37. data/spec/dummy/app/models/like.rb +9 -0
  38. data/spec/dummy/app/presenters/article_presenter.rb +9 -0
  39. data/spec/dummy/app/presenters/author_custom_presenter.rb +5 -0
  40. data/spec/dummy/app/presenters/comment_presenter.rb +13 -0
  41. data/spec/dummy/config/database.yml +3 -3
  42. data/spec/dummy/config/initializers/mongo_mapper_connection.rb +4 -0
  43. data/spec/dummy/config/initializers/vodka_setup.rb +1 -0
  44. data/spec/dummy/config/mongoid.yml +32 -0
  45. data/spec/dummy/config/routes.rb +4 -0
  46. data/spec/dummy/config/thin.yml +1 -1
  47. data/spec/dummy/db/migrate/20130217185353_create_articles.rb +1 -0
  48. data/spec/dummy/db/schema.rb +1 -0
  49. data/spec/dummy/db/seeds.rb +18 -1
  50. data/spec/her/extensions/paginated/paginate_spec.rb +32 -0
  51. data/spec/middleware/response_locale_spec.rb +2 -0
  52. data/spec/requests/relations_spec.rb +9 -0
  53. data/spec/spec_helper.rb +2 -1
  54. data/vodka.gemspec +1 -1
  55. metadata +23 -8
  56. data/lib/vodka/her/extensions/extended_orm.rb +0 -92
  57. data/lib/vodka/her/extensions/will_paginate.rb +0 -32
  58. data/spec/her/extensions/will_paginate/paginate_spec.rb +0 -16
@@ -1,3 +1,5 @@
1
+ services:
2
+ - mongodb
1
3
  rvm:
2
4
  - 1.9.3
3
5
  script: ./script/ci
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
- It currently supports these ORMs on server:
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
- # Defines fields and actions that would be used in :as_json method
76
- present_with :id, :title, :body, :created_at
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 fields and actions that would be used in :as_json method
86
- present_with :id, :body, :author_name, :created_at
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
- # Instead of calling Vodka::Client.configure_her! you may configure her yourself
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
@@ -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!
@@ -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
@@ -23,7 +23,7 @@ module Vodka
23
23
  end
24
24
 
25
25
  def request_signature
26
- Digest::SHA512.hexdigest([request_id, Vodka::Client.config.request_secret].join)
26
+ Vodka::Client.config.digest.hexdigest([request_id, Vodka::Client.config.request_secret].join)
27
27
  end
28
28
  end
29
29
  end
@@ -31,7 +31,7 @@ module Vodka
31
31
  end
32
32
 
33
33
  def expected_response_signature
34
- Digest::SHA512.hexdigest([request_id, Vodka::Client.config.response_secret].join)
34
+ Vodka::Client.config.digest.hexdigest([request_id, Vodka::Client.config.response_secret].join)
35
35
  end
36
36
  end
37
37
  end
@@ -1,6 +1,31 @@
1
1
  module Vodka
2
2
  class Configuration
3
- attr_accessor :request_secret, :response_secret, :api_url
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
- end
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
@@ -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, :fix_params_names, :set_locale
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 = resource_class.find_by_id(params[:id])
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.slice(*resource_class.accessible_attributes.to_a)
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
- response.headers['X-Response-Id'] = vodka_response.id
24
- response.headers['X-Response-Signature'] = vodka_response.signature
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
- if params[:vodka_special_action] == 'first'
7
- resources = [resource_class.first]
8
- elsif params[:vodka_special_action] == 'last'
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
- resource.destroy
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, :request
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
- @request = Rack::Request.new(env)
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
- Digest::SHA512.hexdigest([request_id, Vodka::Server.config.request_secret].join)
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
- Digest::SHA512.hexdigest([request_id, Vodka::Server.config.response_secret].join)
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
- def present_with(*methods)
6
- define_method 'present_vodka' do
7
- json = Hash[methods.map{ |method| [method, send(method)] }]
8
- json[:errors] = errors.messages
9
- json[:metadata] = {}
10
- json
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
- ActiveRecord::Base.send(:extend, Vodka::Server::Plugins::Presentable) if defined?(ActiveRecord)
19
- MongoMapper::Document.send(:extend, Vodka::Server::Plugins::Presentable) if defined?(MongoMapper)
20
- MongoMapper::EmbeddedDocument.send(:extend, Vodka::Server::Plugins::Presentable) if defined?(MongoMapper)
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