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,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