intermodal 0.0.1 → 0.4.0

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 (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,45 @@
1
+ module Intermodal
2
+ module Models
3
+ module HasParentResource
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include Intermodal::Models::Presentation
8
+ end
9
+
10
+ module ClassMethods
11
+ include Models::Accountability::Get
12
+
13
+ def parent_resource(resource, options = {})
14
+ parent_resource_scope = "by_#{resource}_id"
15
+ parent_table_name = options[:parent_table_name] || resource.to_s.pluralize
16
+
17
+ instance_eval do
18
+ if options[:class_name]
19
+ belongs_to resource, :class_name => options[:class_name]
20
+ else
21
+ belongs_to resource
22
+ end
23
+
24
+ validates_presence_of resource
25
+
26
+ scope parent_resource_scope, lambda { |id| where("#{resource}_id" => id) }
27
+ scope "by_#{resource}", lambda { |parent| send(parent_resource_scope, parent.id) }
28
+ scope "by_parent_id", lambda { |id| send(parent_resource_scope, id) }
29
+ scope :by_parent, lambda { |parent| by_parent_id(parent.id) }
30
+
31
+ scope :by_account_id, lambda { |a_id| joins(resource).where( parent_table_name => { :account_id => a_id } ) }
32
+ scope :by_account, lambda { |a| by_account_id(a.id) }
33
+ end
34
+ end
35
+
36
+ def build_get_query(_query, opts)
37
+ _query = super(_query, opts)
38
+ _query = _query.by_parent(opts[:parent]) if opts[:parent]
39
+ _query = _query.by_parent_id(opts[:parent_id]) if opts[:parent_id]
40
+ return _query
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ module Intermodal
2
+ module Models
3
+ module Presentation
4
+ extend ActiveSupport::Concern
5
+
6
+ def as_json(opts = {})
7
+ return presentation(opts) if opts && (opts[:presenter] || opts[:api])
8
+ super(opts)
9
+ end
10
+
11
+ def to_xml(opts = {})
12
+ return presentation(opts.except(:root)).to_xml(opts) if opts && (opts[:presenter] || opts[:api])
13
+ super(opts)
14
+ end
15
+
16
+ def presenter(opts)
17
+ opts[:presenter] || opts[:api].presenter_for(self.class)
18
+ end
19
+
20
+ def presentation(opts)
21
+ presenter(opts).call(self, opts)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,60 @@
1
+ module Intermodal
2
+ module Models
3
+ module RedisAccessToken
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_accessor :account_id, :token
8
+ cattr_accessor :redis
9
+
10
+ EXPIRATION_PERIOD = 24 unless defined?(EXPIRATION_PERIOD)# hours
11
+ TOKEN_SIZE = 32 unless defined?(TOKEN_SIZE)
12
+ REDIS = Redis.new unless defined?(REDIS)
13
+
14
+ def self.establish_connection!(address = 'localhost:6379')
15
+ host, port = address.split(':')
16
+
17
+ host ||= 'localhost'
18
+ port = (port || 6379).to_i
19
+
20
+ self.redis = Redis.new(:host => host, :port => port)
21
+ end
22
+
23
+ def self.authenticate!(token)
24
+ return nil unless account_id = self.redis.get("auth:#{token.to_s}")
25
+ ::Account.where(:id => account_id).first
26
+ end
27
+
28
+ def self.generate!(account)
29
+ token = new(:account_id => account.id)
30
+ begin
31
+ token.token = SecureRandom.hex(TOKEN_SIZE)
32
+ end until token.valid?
33
+ return token if token.save
34
+ end
35
+
36
+ def self.count
37
+ redis.keys('auth:*').size
38
+ end
39
+
40
+ def initialize(opts)
41
+ @account_id = opts[:account_id]
42
+ end
43
+
44
+ def redis_key
45
+ "auth:#{self.token.to_s}"
46
+ end
47
+
48
+ def valid?
49
+ !redis.get(redis_key)
50
+ end
51
+
52
+ def save
53
+ redis.setex(redis_key, EXPIRATION_PERIOD.hours, account_id)
54
+ end
55
+
56
+ def to_s; self.token; end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,126 @@
1
+ module Intermodal
2
+ module Models
3
+ module ResourceLinking
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include Intermodal::Models::HasParentResource
8
+ extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ include HasParentResource::ClassMethods
13
+
14
+ def links_resource(_parent, options = {})
15
+ raise "Must supply :to" unless options[:to]
16
+
17
+ _target_resource = options[:to]
18
+ _target_resource_class_name = options[:class_name]
19
+ _target_table_name = options[:target_table_name] || _target_resource.to_s.pluralize
20
+ _parent_resource_class_name = options[:parent_class_name]
21
+ _parent_table_name = options[:parent_table_name] || _parent.to_s.pluralize
22
+
23
+ instance_eval do
24
+ parent_resource(_parent, :class_name => _parent_resource_class_name)
25
+
26
+ # Associations
27
+ if _target_resource_class_name
28
+ belongs_to _target_resource, :class_name => _target_resource_class_name
29
+ else
30
+ belongs_to _target_resource
31
+ end
32
+
33
+ # Scopes
34
+ scope "by_#{_target_resource}_id", lambda { |_id| where("#{target_resource_name}_id" => _id) }
35
+ scope :by_target_id, lambda { |_id| send("by_#{target_resource_name}_id", _id) }
36
+ #scope :by_target_account_id, lambda { |a_id|
37
+ # joins(_target_resource).
38
+ # where(_target_table_name => { :account_id => a_id })}
39
+ #scope :by_target_account, lambda { |a| by_target_account_id(a.id) }
40
+ #scope :_by_target_account_id, lambda { |a_id| where(_target_table_name => { :account_id => a_id})}
41
+
42
+ # Transformations
43
+ def self.to_target_ids
44
+ select("#{target_resource_name}_id").map(&"#{target_resource_name}_id".to_sym)
45
+ end
46
+
47
+ # Links parent resource and target resource
48
+ class_attribute :target_resource_name, :target_resource_class_name, :parent_resource_name, :parent_resource_class_name
49
+
50
+ def self.parent_model
51
+ (parent_resource_class_name || parent_resource_name.to_s.camelize).constantize
52
+ end
53
+
54
+ def self.target_model
55
+ (target_resource_class_name || target_resource_name.to_s.camelize).constantize
56
+ end
57
+
58
+ singleton_class.instance_eval do
59
+ define_method "to_#{_target_resource}_ids" do
60
+ to_target_ids
61
+ end
62
+ end
63
+ end
64
+
65
+ # Set class inheritable variables
66
+ self.target_resource_name = _target_resource
67
+ self.target_resource_class_name = _target_resource_class_name
68
+ self.parent_resource_name = _parent
69
+ self.parent_resource_class_name = _parent_resource_class_name
70
+ end
71
+
72
+ def build_get_query(_query, opts)
73
+ _query = super(_query, opts)
74
+ #_query.build_arel
75
+ #_query = _query.by_target_account(opts[:account]) if opts[:account]
76
+ #_query = _query.by_target_account_id(opts[:account_id]) if opts[:account_id]
77
+ # HACK:
78
+ # Workaround for Arel bug
79
+ #_query = _query._by_target_account_id(opts[:account_id]) if opts[:account_id]
80
+ return _query
81
+ end
82
+
83
+ # Standard Methods for all linking resources
84
+
85
+ # .replace
86
+ # - replaces existing resources with new resource ids
87
+ # - existing resources gets deleted
88
+ def replace(parent_id, resource_ids, options = {})
89
+ transaction do
90
+ parent = parent_model.get(parent_id, options.except(:parent, :parent_id))
91
+ return nil unless parent
92
+
93
+ targets = target_model.get(:all, options).where(:id => resource_ids)
94
+ parent.send("#{target_resource_name.to_s.pluralize}=", targets)
95
+ by_parent_id(parent_id).to_target_ids
96
+ end
97
+ end
98
+
99
+ # .append
100
+ # - adds new resources into the list
101
+ # - does not add redundent resources
102
+ def append(parent_id, resource_ids, options = {})
103
+ transaction do
104
+ parent = parent_model.get(parent_id, options.except(:parent, :parent_id))
105
+ return nil unless parent
106
+
107
+ resource_ids = resource_ids - by_parent_id(parent_id).by_target_id(resource_ids).to_target_ids
108
+ parent.send("#{target_resource_name.to_s.pluralize}") << target_model.get(:all, options).where(:id => resource_ids)
109
+ end
110
+ end
111
+
112
+ # .remove
113
+ # - removes specified lists
114
+ def remove(parent_id, resource_ids, options = {})
115
+ transaction do
116
+ parent = parent_model.get(parent_id, options.except(:parent, :parent_id))
117
+ return nil unless parent
118
+
119
+ by_parent_id(parent_id).by_target_id(resource_ids).delete_all
120
+ by_parent_id(parent_id).to_target_ids
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,35 @@
1
+ require 'rails-html-sanitizer'
2
+
3
+ module Intermodal
4
+ module Models
5
+ module SanitizeHTML
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ # Whitelist sanitizer
10
+ # sanitize_html :description
11
+ # sanitize_html :description, tags: %w(table tr td), attributes: %w(id class style)
12
+ def self.sanitize_html(field, options={})
13
+ before_save do
14
+ # Only sanitize if there is something to sanitize
15
+ if self.send(field) and self.send("#{field}_changed?")
16
+ self.send("#{field}=", Rails::Html::WhiteListSanitizer.new.sanitize(self.send(field), options))
17
+ end
18
+ end
19
+ end
20
+
21
+ # Completely strips out all HTML tags
22
+ # strip_html :title
23
+ def self.strip_html(field)
24
+ before_save do
25
+ # Only strip if there is something to sanitize
26
+ if self.send(field) and self.send("#{field}_changed?")
27
+ self.send("#{field}=", Rails::Html::FullSanitizer.new.sanitize(self.send(field)))
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ module Intermodal
2
+ module Presenters
3
+ module NamedResource
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include Intermodal::Presenters::Resource
8
+ presents :name
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module Intermodal
2
+ module Presenters
3
+ module Resource
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ presents :id
8
+
9
+ presents :created_at
10
+ presents :updated_at
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ require 'intermodal/rack/rescue'
2
+
3
+ module Intermodal
4
+ module Concerns
5
+ module Rails
6
+ module Rails3Stack
7
+ extend ActiveSupport::Concern
8
+ # Override the middleware stack to something more streamlined
9
+
10
+ included do
11
+ def default_middleware_stack
12
+ ActionDispatch::MiddlewareStack.new.tap do |middleware|
13
+ #middleware.use ::Rack::Lock unless config.allow_concurrency
14
+ middleware.use ::Rack::Runtime
15
+ middleware.use ::Intermodal::Rack::Rescue
16
+ middleware.use ::Rails::Rack::Logger
17
+ middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
18
+ middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
19
+ middleware.use ::ActionDispatch::Callbacks #, !config.cache_classes
20
+
21
+ if config.session_store
22
+ middleware.use config.session_store, config.session_options
23
+ middleware.use ::ActionDispatch::Flash
24
+ end
25
+
26
+ middleware.use ::ActionDispatch::ParamsParser
27
+ middleware.use ::Rack::MethodOverride
28
+ middleware.use ::ActionDispatch::Head
29
+ end
30
+ end
31
+
32
+ # Query caching doesn't work well with what we are doing
33
+ # Maybe this has changed since Rails 4.
34
+ initializer 'intermodal_example.hack_active_record', :after => 'eager_load!' do
35
+ config.middleware.delete ActiveRecord::QueryCache
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ module Intermodal
2
+ module Concerns
3
+ module Rails
4
+ module Rails4Stack
5
+ extend ActiveSupport::Concern
6
+
7
+ # Delete extraneous middleware
8
+
9
+ included do
10
+ config.middleware.delete 'ActiveRecord::QueryCache'
11
+ config.middleware.delete 'ActionDispatch::Cookies'
12
+ config.middleware.delete 'ActionDispatch::Session::CookieStore'
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ require 'intermodal/rack/auth'
2
+
3
+ # Use this to configure Warden to use X_AUTH_TOKEN strategy
4
+ module Intermodal
5
+ module Concerns
6
+ module Rails
7
+ module UseWarden
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ config.middleware.use Warden::Manager do |manager|
12
+ manager.default_strategies :x_auth_token #, :basic
13
+ manager.failure_app = proc do
14
+ Intermodal::Rack::Auth::UNAUTHORIZED
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module Intermodal
2
+ @max_per_page = 100
3
+ @default_per_page = 10
4
+
5
+ #TODO: Refactor to create a config DSL for modules
6
+ def self.max_per_page(value = nil)
7
+ return @max_per_page unless value
8
+ @max_per_page = value
9
+ end
10
+
11
+ def self.default_per_page(value = nil)
12
+ return @default_per_page unless value
13
+ @default_per_page = value
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Intermodal
2
+ class Configuration < ::Rails::Engine::Configuration
3
+ attr_accessor :allow_concurrency
4
+
5
+ def initialize(*)
6
+ super
7
+ self.encoding = 'utf-8'
8
+ @allow_concurrency = false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ require 'responders'
2
+
3
+ module Intermodal
4
+ class APIController < ActionController::Metal
5
+
6
+ include AbstractController::Rendering
7
+ include ActionController::Rendering
8
+ include ActionController::Renderers
9
+ include ActionController::RackDelegation
10
+ include ActionController::Head
11
+ include ActionController::MimeResponds
12
+
13
+ include ActionController::Instrumentation
14
+ include AbstractController::Callbacks
15
+ include ActionController::Rescue
16
+
17
+ include ActionController::RespondWith
18
+ include Intermodal::Controllers::Presentation
19
+
20
+ use_renderers :json, :xml
21
+
22
+ self.responder = Intermodal::ResourceResponder
23
+
24
+ ActiveSupport.run_load_hooks(:intermodal_api_controller, self)
25
+ end
26
+ end