vodka 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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