intermodal 0.0.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +1 -1
  5. data/README +6 -4
  6. data/Rakefile +6 -0
  7. data/intermodal.gemspec +51 -0
  8. data/lib/generators/intermodal_generator.rb +8 -0
  9. data/lib/intermodal.rb +122 -0
  10. data/lib/intermodal/api.rb +246 -0
  11. data/lib/intermodal/api/configuration.rb +40 -0
  12. data/lib/intermodal/api/railties.rb +21 -0
  13. data/lib/intermodal/concerns/acceptors/named_resource.rb +12 -0
  14. data/lib/intermodal/concerns/acceptors/resource.rb +12 -0
  15. data/lib/intermodal/concerns/controllers/accountability.rb +17 -0
  16. data/lib/intermodal/concerns/controllers/anonymous.rb +24 -0
  17. data/lib/intermodal/concerns/controllers/authenticatable.rb +24 -0
  18. data/lib/intermodal/concerns/controllers/paginated_collection.rb +25 -0
  19. data/lib/intermodal/concerns/controllers/presentation.rb +17 -0
  20. data/lib/intermodal/concerns/controllers/resource.rb +51 -0
  21. data/lib/intermodal/concerns/controllers/resource_linking.rb +58 -0
  22. data/lib/intermodal/concerns/let.rb +21 -0
  23. data/lib/intermodal/concerns/models/access_credential.rb +28 -0
  24. data/lib/intermodal/concerns/models/account.rb +16 -0
  25. data/lib/intermodal/concerns/models/accountability.rb +47 -0
  26. data/lib/intermodal/concerns/models/db_access_token.rb +29 -0
  27. data/lib/intermodal/concerns/models/has_parent_resource.rb +45 -0
  28. data/lib/intermodal/concerns/models/presentation.rb +25 -0
  29. data/lib/intermodal/concerns/models/redis_access_token.rb +60 -0
  30. data/lib/intermodal/concerns/models/resource_linking.rb +126 -0
  31. data/lib/intermodal/concerns/models/sanitize_html.rb +35 -0
  32. data/lib/intermodal/concerns/presenters/named_resource.rb +12 -0
  33. data/lib/intermodal/concerns/presenters/resource.rb +14 -0
  34. data/lib/intermodal/concerns/rails/rails_3_stack.rb +42 -0
  35. data/lib/intermodal/concerns/rails/rails_4_stack.rb +17 -0
  36. data/lib/intermodal/concerns/rails/use_warden.rb +21 -0
  37. data/lib/intermodal/config.rb +15 -0
  38. data/lib/intermodal/configuration.rb +11 -0
  39. data/lib/intermodal/controllers/api_controller.rb +26 -0
  40. data/lib/intermodal/controllers/linking_resource_controller.rb +8 -0
  41. data/lib/intermodal/controllers/nested_resource_controller.rb +18 -0
  42. data/lib/intermodal/controllers/resource_controller.rb +11 -0
  43. data/lib/intermodal/dsl/controllers.rb +125 -0
  44. data/lib/intermodal/dsl/mapping.rb +79 -0
  45. data/lib/intermodal/dsl/presentation_helpers.rb +107 -0
  46. data/lib/intermodal/mapping/acceptor.rb +2 -2
  47. data/lib/intermodal/mapping/mapper.rb +39 -13
  48. data/lib/intermodal/mapping/presenter.rb +12 -6
  49. data/lib/intermodal/proxies/linking_resources.rb +58 -0
  50. data/lib/intermodal/proxies/will_paginate.rb +85 -0
  51. data/lib/intermodal/rack/auth.rb +29 -0
  52. data/lib/intermodal/rack/dummy_store.rb +24 -0
  53. data/lib/intermodal/rack/rescue.rb +82 -0
  54. data/lib/intermodal/responders/linking_resource_responder.rb +21 -0
  55. data/lib/intermodal/responders/resource_responder.rb +64 -0
  56. data/lib/intermodal/rspec/acceptors.rb +79 -0
  57. data/lib/intermodal/rspec/models/accountability.rb +114 -0
  58. data/lib/intermodal/rspec/models/has_parent_resource.rb +132 -0
  59. data/lib/intermodal/rspec/models/resource_linking.rb +234 -0
  60. data/lib/intermodal/rspec/models/sanitization.rb +84 -0
  61. data/lib/intermodal/rspec/presenters.rb +92 -0
  62. data/lib/intermodal/rspec/requests/authenticated_requests.rb +17 -0
  63. data/lib/intermodal/rspec/requests/linked_resources.rb +180 -0
  64. data/lib/intermodal/rspec/requests/paginated_collection.rb +60 -0
  65. data/lib/intermodal/rspec/requests/rack.rb +142 -0
  66. data/lib/intermodal/rspec/requests/request_validations.rb +36 -0
  67. data/lib/intermodal/rspec/requests/resources.rb +275 -0
  68. data/lib/intermodal/rspec/requests/rfc2616_status_codes.rb +51 -0
  69. data/lib/intermodal/rspec/validators.rb +86 -0
  70. data/lib/intermodal/validators/account_validator.rb +27 -0
  71. data/lib/intermodal/validators/different_account_validator.rb +27 -0
  72. data/lib/intermodal/version.rb +3 -0
  73. data/spec/mapping/acceptors_spec.rb +142 -0
  74. data/spec/mapping/presenters_spec.rb +186 -0
  75. data/spec/models/accountability_spec.rb +13 -0
  76. data/spec/models/has_parent_resource_spec.rb +18 -0
  77. data/spec/models/resource_linking_spec.rb +21 -0
  78. data/spec/proxies/will_paginate_spec.rb +163 -0
  79. data/spec/rack/auth_spec.rb +51 -0
  80. data/spec/requests/linked_resources.rb +37 -0
  81. data/spec/requests/nested_resources_spec.rb +54 -0
  82. data/spec/requests/resources_spec.rb +50 -0
  83. data/spec/spec_helper.rb +53 -0
  84. data/spec/support/api.rb +50 -0
  85. data/spec/support/app/class_builder.rb +41 -0
  86. data/spec/support/app/db/adapter_helper.rb +53 -0
  87. data/spec/support/app/db/authentication_schema_helper.rb +62 -0
  88. data/spec/support/app/db/migration_helper.rb +44 -0
  89. data/spec/support/app/schema.rb +101 -0
  90. data/spec/support/application.rb +23 -0
  91. data/spec/support/blueprints.rb +41 -0
  92. data/spec/support/epiphyte.rb +29 -0
  93. metadata +393 -52
  94. data/lib/intermodal/base.rb +0 -13
  95. data/lib/intermodal/declare_controllers.rb +0 -102
  96. data/lib/intermodal/mapping.rb +0 -4
  97. data/lib/intermodal/mapping/dsl.rb +0 -76
@@ -0,0 +1,40 @@
1
+ require 'rails/railtie/configuration'
2
+
3
+
4
+ # Modified from Rails 4.2 rails/engine/configuration.rg
5
+ module Intermodal
6
+ class API
7
+ class Configuration < ::Rails::Railtie::Configuration
8
+ attr_writer :middleware
9
+
10
+ def initialize
11
+ super()
12
+ @generators = app_generators.dup
13
+ end
14
+
15
+ # Returns the middleware stack for the engine.
16
+ def middleware
17
+ @middleware ||= Rails::Configuration::MiddlewareStackProxy.new
18
+ end
19
+
20
+ # Holds generators configuration:
21
+ #
22
+ # config.generators do |g|
23
+ # g.orm :data_mapper, migration: true
24
+ # g.template_engine :haml
25
+ # g.test_framework :rspec
26
+ # end
27
+ #
28
+ # If you want to disable color in console, do:
29
+ #
30
+ # config.generators.colorize_logging = false
31
+ #
32
+ def generators #:nodoc:
33
+ @generators ||= Rails::Configuration::Generators.new
34
+ yield(@generators) if block_given?
35
+ @generators
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ module Rails
2
+ class Engine < Railtie
3
+ class Railties
4
+ include Enumerable
5
+ attr_reader :_all
6
+
7
+ def initialize
8
+ @_all ||= ::Rails::Railtie.subclasses.map(&:instance) +
9
+ ::Rails::Engine.subclasses.map(&:instance)
10
+ end
11
+
12
+ def each(*args, &block)
13
+ _all.each(*args, &block)
14
+ end
15
+
16
+ def -(others)
17
+ _all - others
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ module Intermodal
2
+ module Acceptors
3
+ module NamedResource
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include Intermodal::Acceptors::Resource
8
+ accepts :name
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Intermodal
2
+ module Acceptors
3
+ module Resource
4
+ extend ActiveSupport::Concern
5
+
6
+ # Explicit blacklist
7
+ included do
8
+ exclude_from_acceptance :id, :created_at, :updated_at, :account_id
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ require 'intermodal/concerns/controllers/authenticatable'
2
+
3
+ module Intermodal
4
+ module Controllers
5
+
6
+ # This concern builds on top of Intermodal::Controllers::Authenticatable
7
+ # by requiring authentication for all controller actions.
8
+ module Accountability
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Intermodal::Controllers::Authenticatable
13
+ before_filter :authentication
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module Intermodal
2
+ module Controllers
3
+ module Anonymous
4
+ extend ActiveSupport::Concern
5
+
6
+ protected
7
+
8
+ # Hacks to get anonymous controllers working
9
+ def reloadable?(app)
10
+ false
11
+ end
12
+
13
+ module ClassMethods
14
+ def anonymous?
15
+ true
16
+ end
17
+
18
+ def controller_path
19
+ 'anonymous_web_service'
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require 'warden'
2
+
3
+ module Intermodal
4
+ module Controllers
5
+ # This concern adds an interface for warden, allowing access to
6
+ # the warden user as well as exposing a method to ask warden
7
+ # for authentication.
8
+ module Authenticatable
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Intermodal::Let
13
+
14
+ let(:account) { env['warden'].user }
15
+ end
16
+
17
+ protected
18
+
19
+ def authentication
20
+ throw(:warden) unless env['warden'].authenticate?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module Intermodal
2
+ module Controllers
3
+ module PaginatedCollection
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ let(:collection) { raise 'You must define collection' }
8
+ let(:collection_name) { self.class.collection_name.to_s } # TODO: This might already be defined in Rails 3.x
9
+ let(:presented_collection) { collection.paginate :page => params[:page], :per_page => per_page }
10
+
11
+ let(:per_page) do
12
+ if params[:per_page]
13
+ params[:per_page].to_i <= api.max_per_page ? params[:per_page] : api.max_per_page
14
+ else
15
+ api.default_per_page
16
+ end
17
+ end
18
+ end
19
+
20
+ def index
21
+ respond_with presented_collection, :presentation_root => collection_name, :presentation_scope => presentation_scope_for_index
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module Intermodal
2
+ module Controllers
3
+ module Presentation
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include Intermodal::Let
8
+
9
+ let(:api) { self.class.api }
10
+ let(:presenter) { api.presenters[model_name] }
11
+ let(:acceptor) { api.acceptors[model_name] }
12
+ let(:accepted_params) { acceptor.call(params || {}) }
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,51 @@
1
+ module Intermodal
2
+ module Controllers
3
+ module Resource
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include Intermodal::Controllers::Accountability
8
+ include Intermodal::Controllers::Anonymous
9
+ include Intermodal::Controllers::PaginatedCollection
10
+
11
+ respond_to :json
12
+
13
+ class_attribute :model, :collection_name, :api
14
+
15
+ let(:resource) { raise 'You must define resource' }
16
+
17
+ let(:model) { self.class.model || self.class.collection_name.to_s.classify.constantize }
18
+ let(:resource_name) {collection_name.singularize }
19
+ let(:model_name) { model.name.underscore.to_sym }
20
+
21
+ # Wrap JSON with root key?
22
+ let(:presentation_root) { nil }
23
+ let(:presentation_scope) { nil } # Will default to :default scope
24
+ let(:presentation_scope_for_index) { nil }
25
+ let(:always_nest_collections) { false }
26
+ end
27
+
28
+ # Actions
29
+
30
+ # index defined in PaginatedCollection
31
+
32
+ def show
33
+ respond_with resource
34
+ end
35
+
36
+ def create
37
+ respond_with model.create(create_params)
38
+ end
39
+
40
+ def update
41
+ resource.update_attributes(update_params)
42
+ respond_with resource
43
+ end
44
+
45
+ def destroy
46
+ resource.destroy
47
+ respond_with resource
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,58 @@
1
+ module Intermodal
2
+ module Controllers
3
+ module ResourceLinking
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :api
8
+
9
+ include Intermodal::Controllers::Accountability
10
+ include Intermodal::Controllers::Anonymous
11
+
12
+ respond_to :json
13
+ self.responder = ResourceResponder
14
+
15
+ let(:parent_id) { params[:id] }
16
+ let(:collection_name) { self.class.collection_name.to_s } # TODO: This might already be defined in Rails 3.x
17
+ let(:collection) { model.get(:all, :parent_id => parent_id).to_target_ids }
18
+
19
+ # :target_element_name: Params key for list of linked resource ids
20
+ let(:target_element_name) { "#{target_resource_name.to_s.singularize}_ids" }
21
+
22
+ # :accepted_params: All attribute and values for resource
23
+ let(:accepted_params) { params[parent_resource_name] || {} }
24
+
25
+ # :target_ids: List of ids extracted from params
26
+ let(:target_ids) { accepted_params[target_element_name] }
27
+
28
+ let(:presentation_root) { parent_resource_name }
29
+ let(:presentation_scope) { nil } # Will default to :default scope
30
+ let(:presented_collection) do
31
+ Intermodal::Proxies::LinkingResources.new presentation_root,
32
+ :to => target_resource_name,
33
+ :with => collection,
34
+ :parent_id => parent_id
35
+ end
36
+ end
37
+
38
+ def index
39
+ respond_with presented_collection
40
+ end
41
+
42
+ def create
43
+ return head :status => 422 unless target_ids
44
+ respond_with model.replace(params[:id], target_ids, :account => account), :location => nil
45
+ end
46
+
47
+ def update
48
+ return head :status => 422 unless target_ids
49
+ respond_with(model.append(params[:id], target_ids, :account => account))
50
+ end
51
+
52
+ def destroy
53
+ return head :status => 422 unless target_ids
54
+ respond_with(model.remove(params[:id], target_ids, :account => account))
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,21 @@
1
+ # Ripped from Rspec 2.0 beta.12
2
+ module Intermodal
3
+ module Let
4
+ extend ActiveSupport::Concern
5
+
6
+ private
7
+
8
+ def __memoized # :nodoc:
9
+ @__memoized ||= {}
10
+ end
11
+
12
+ module ClassMethods
13
+ def let(name, &block)
14
+ define_method(name) do
15
+ __memoized[name] ||= instance_eval(&block)
16
+ end
17
+ protected(name)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ require 'active_model/secure_password'
2
+
3
+ module Intermodal
4
+ module Models
5
+ module AccessCredential
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Intermodal::Models::Accountability
10
+ include ActiveModel::SecurePassword
11
+
12
+ has_secure_password
13
+
14
+ # Validations
15
+ validates_uniqueness_of :identity
16
+
17
+ # Associations
18
+ belongs_to :account
19
+
20
+ def self.authenticate!(identity, key)
21
+ access = self.find_by(identity: identity).try(:authenticate, key)
22
+ return false unless access
23
+ access.account
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ module Intermodal
2
+ module Models
3
+ module Account
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Validations
8
+ validates_presence_of :name
9
+
10
+ # Associations
11
+ has_many :access_tokens
12
+ has_many :access_credentials
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,47 @@
1
+ module Intermodal
2
+ module Models
3
+ module Accountability
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # TODO: This might not be necessary
8
+ include Intermodal::Models::Presentation
9
+
10
+ # Validations
11
+ validates_presence_of :account_id
12
+
13
+ # Associations
14
+ belongs_to :account
15
+
16
+ # Scopes
17
+ scope :by_account_id, lambda { |a_id| where(:account_id => a_id) }
18
+ scope :by_account, lambda { |a| by_account_id(a.id) }
19
+
20
+ extend Get
21
+ end
22
+
23
+ module Get
24
+ # API: overrideable
25
+ # Use this to extend Get
26
+ def build_get_query(_query, opts)
27
+ _query = _query.by_account(opts[:account]) if opts[:account]
28
+ _query = _query.by_account_id(opts[:account_id]) if opts[:account_id]
29
+ return _query
30
+ end
31
+
32
+ def get(_id, opts = {})
33
+ # Rails 4 no longer has .scoped, and where(nil) is the actual replacement
34
+ # always returning a relation
35
+ # See: http://stackoverflow.com/a/18199294/313193
36
+ _query = build_get_query(self.where(nil), opts)
37
+ return _query if _id == :all
38
+
39
+ # Force false on readonly
40
+ _resource = _query.where(:id => _id).limit(1).readonly(false).first
41
+ raise ActiveRecord::RecordNotFound, "Could not find resource" unless _resource
42
+ return _resource
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,29 @@
1
+ module Intermodal
2
+ module Models
3
+ module DbAccessToken
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ TOKEN_SIZE = 32
8
+
9
+ # Validations
10
+ validates_uniqueness_of :token
11
+
12
+ # Associations
13
+ belongs_to :account
14
+
15
+ def self.authenticate!(token)
16
+ ::Account.joins(:access_tokens).where(:access_tokens => { :token => token }).limit(1).first
17
+ end
18
+
19
+ def self.generate!(account)
20
+ token = new(:account_id => account.id)
21
+ begin
22
+ token.token = SecureRandom.hex(TOKEN_SIZE)
23
+ end until token.valid?
24
+ return token.token if token.save
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end