intermodal 0.0.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/LICENSE +1 -1
- data/README +6 -4
- data/Rakefile +6 -0
- data/intermodal.gemspec +51 -0
- data/lib/generators/intermodal_generator.rb +8 -0
- data/lib/intermodal.rb +122 -0
- data/lib/intermodal/api.rb +246 -0
- data/lib/intermodal/api/configuration.rb +40 -0
- data/lib/intermodal/api/railties.rb +21 -0
- data/lib/intermodal/concerns/acceptors/named_resource.rb +12 -0
- data/lib/intermodal/concerns/acceptors/resource.rb +12 -0
- data/lib/intermodal/concerns/controllers/accountability.rb +17 -0
- data/lib/intermodal/concerns/controllers/anonymous.rb +24 -0
- data/lib/intermodal/concerns/controllers/authenticatable.rb +24 -0
- data/lib/intermodal/concerns/controllers/paginated_collection.rb +25 -0
- data/lib/intermodal/concerns/controllers/presentation.rb +17 -0
- data/lib/intermodal/concerns/controllers/resource.rb +51 -0
- data/lib/intermodal/concerns/controllers/resource_linking.rb +58 -0
- data/lib/intermodal/concerns/let.rb +21 -0
- data/lib/intermodal/concerns/models/access_credential.rb +28 -0
- data/lib/intermodal/concerns/models/account.rb +16 -0
- data/lib/intermodal/concerns/models/accountability.rb +47 -0
- data/lib/intermodal/concerns/models/db_access_token.rb +29 -0
- data/lib/intermodal/concerns/models/has_parent_resource.rb +45 -0
- data/lib/intermodal/concerns/models/presentation.rb +25 -0
- data/lib/intermodal/concerns/models/redis_access_token.rb +60 -0
- data/lib/intermodal/concerns/models/resource_linking.rb +126 -0
- data/lib/intermodal/concerns/models/sanitize_html.rb +35 -0
- data/lib/intermodal/concerns/presenters/named_resource.rb +12 -0
- data/lib/intermodal/concerns/presenters/resource.rb +14 -0
- data/lib/intermodal/concerns/rails/rails_3_stack.rb +42 -0
- data/lib/intermodal/concerns/rails/rails_4_stack.rb +17 -0
- data/lib/intermodal/concerns/rails/use_warden.rb +21 -0
- data/lib/intermodal/config.rb +15 -0
- data/lib/intermodal/configuration.rb +11 -0
- data/lib/intermodal/controllers/api_controller.rb +26 -0
- data/lib/intermodal/controllers/linking_resource_controller.rb +8 -0
- data/lib/intermodal/controllers/nested_resource_controller.rb +18 -0
- data/lib/intermodal/controllers/resource_controller.rb +11 -0
- data/lib/intermodal/dsl/controllers.rb +125 -0
- data/lib/intermodal/dsl/mapping.rb +79 -0
- data/lib/intermodal/dsl/presentation_helpers.rb +107 -0
- data/lib/intermodal/mapping/acceptor.rb +2 -2
- data/lib/intermodal/mapping/mapper.rb +39 -13
- data/lib/intermodal/mapping/presenter.rb +12 -6
- data/lib/intermodal/proxies/linking_resources.rb +58 -0
- data/lib/intermodal/proxies/will_paginate.rb +85 -0
- data/lib/intermodal/rack/auth.rb +29 -0
- data/lib/intermodal/rack/dummy_store.rb +24 -0
- data/lib/intermodal/rack/rescue.rb +82 -0
- data/lib/intermodal/responders/linking_resource_responder.rb +21 -0
- data/lib/intermodal/responders/resource_responder.rb +64 -0
- data/lib/intermodal/rspec/acceptors.rb +79 -0
- data/lib/intermodal/rspec/models/accountability.rb +114 -0
- data/lib/intermodal/rspec/models/has_parent_resource.rb +132 -0
- data/lib/intermodal/rspec/models/resource_linking.rb +234 -0
- data/lib/intermodal/rspec/models/sanitization.rb +84 -0
- data/lib/intermodal/rspec/presenters.rb +92 -0
- data/lib/intermodal/rspec/requests/authenticated_requests.rb +17 -0
- data/lib/intermodal/rspec/requests/linked_resources.rb +180 -0
- data/lib/intermodal/rspec/requests/paginated_collection.rb +60 -0
- data/lib/intermodal/rspec/requests/rack.rb +142 -0
- data/lib/intermodal/rspec/requests/request_validations.rb +36 -0
- data/lib/intermodal/rspec/requests/resources.rb +275 -0
- data/lib/intermodal/rspec/requests/rfc2616_status_codes.rb +51 -0
- data/lib/intermodal/rspec/validators.rb +86 -0
- data/lib/intermodal/validators/account_validator.rb +27 -0
- data/lib/intermodal/validators/different_account_validator.rb +27 -0
- data/lib/intermodal/version.rb +3 -0
- data/spec/mapping/acceptors_spec.rb +142 -0
- data/spec/mapping/presenters_spec.rb +186 -0
- data/spec/models/accountability_spec.rb +13 -0
- data/spec/models/has_parent_resource_spec.rb +18 -0
- data/spec/models/resource_linking_spec.rb +21 -0
- data/spec/proxies/will_paginate_spec.rb +163 -0
- data/spec/rack/auth_spec.rb +51 -0
- data/spec/requests/linked_resources.rb +37 -0
- data/spec/requests/nested_resources_spec.rb +54 -0
- data/spec/requests/resources_spec.rb +50 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/support/api.rb +50 -0
- data/spec/support/app/class_builder.rb +41 -0
- data/spec/support/app/db/adapter_helper.rb +53 -0
- data/spec/support/app/db/authentication_schema_helper.rb +62 -0
- data/spec/support/app/db/migration_helper.rb +44 -0
- data/spec/support/app/schema.rb +101 -0
- data/spec/support/application.rb +23 -0
- data/spec/support/blueprints.rb +41 -0
- data/spec/support/epiphyte.rb +29 -0
- metadata +393 -52
- data/lib/intermodal/base.rb +0 -13
- data/lib/intermodal/declare_controllers.rb +0 -102
- data/lib/intermodal/mapping.rb +0 -4
- data/lib/intermodal/mapping/dsl.rb +0 -76
@@ -5,23 +5,29 @@ module Intermodal
|
|
5
5
|
self._mapping_strategy = INCLUDE_NILS
|
6
6
|
|
7
7
|
class << self
|
8
|
-
alias exclude_from_presentation exclude_properties
|
8
|
+
alias exclude_from_presentation exclude_properties
|
9
9
|
|
10
10
|
# Convenience alias for maps
|
11
11
|
# Examples:
|
12
12
|
#
|
13
13
|
# presents :name
|
14
|
-
# presents :name, :
|
15
|
-
# presents :name, :
|
16
|
-
|
14
|
+
# presents :name, :with => lambda { |o| o.name.camelize }
|
15
|
+
# presents :name, :with => [ :first_name, :last_name, :suffix, :prefix ]
|
16
|
+
|
17
17
|
alias presents maps
|
18
18
|
|
19
19
|
def model_name(resource)
|
20
20
|
resource.class.name.demodulize.underscore
|
21
21
|
end
|
22
22
|
|
23
|
-
def call(resource)
|
24
|
-
|
23
|
+
def call(resource, options = {})
|
24
|
+
_scope = options[:scope] || :default
|
25
|
+
|
26
|
+
if options[:root] || options[:always_nest_collections]
|
27
|
+
{ (options[:root].is_a?(TrueClass) || options[:root].nil? ? model_name(resource) : options[:root]) => map_attributes(resource, _scope) }
|
28
|
+
else
|
29
|
+
map_attributes(resource, _scope)
|
30
|
+
end
|
25
31
|
end
|
26
32
|
end
|
27
33
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Intermodal
|
2
|
+
module Proxies
|
3
|
+
# This class is necessary to create the correct output for linked resources.
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# books n-to-n authors
|
7
|
+
# Book #1 has 3 authors, Author #1, #2, and #3
|
8
|
+
#
|
9
|
+
# Expected output:
|
10
|
+
#
|
11
|
+
# { "books": { "author_ids": [ 1, 2, 3 ] } }
|
12
|
+
#
|
13
|
+
# <books>
|
14
|
+
# <author_ids>
|
15
|
+
# <author_id>1</author_id>
|
16
|
+
# <author_id>2</author_id>
|
17
|
+
# <author_id>3</author_id>
|
18
|
+
# </author_ids>
|
19
|
+
# </books>
|
20
|
+
#
|
21
|
+
class LinkingResources
|
22
|
+
attr_accessor :parent_resource_name, :linked_resource_name, :collection, :parent_id
|
23
|
+
delegate :to_a, :to => :collection
|
24
|
+
|
25
|
+
# USAGE:
|
26
|
+
# Intermodal::Proxies::LinkingResources.new(:parent, :to => :linked_resources, :with => collection)
|
27
|
+
def initialize(parent_resource_name, options = {})
|
28
|
+
@parent_resource_name = parent_resource_name
|
29
|
+
@collection = options[:with]
|
30
|
+
@linked_resource_name = options[:to]
|
31
|
+
@parent_id = (options[:parent_id] ? options[:parent_id].to_i : nil ) # nil should be nil, not 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_json(options = {})
|
35
|
+
as_json(options).to_json
|
36
|
+
end
|
37
|
+
|
38
|
+
def as_json(options = {})
|
39
|
+
root = options[:root] || parent_resource_name
|
40
|
+
(root ? { root => presentation } : presentation)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_xml(options = {})
|
44
|
+
root = options[:root] || parent_resource_name
|
45
|
+
presentation.to_xml(:root => root)
|
46
|
+
end
|
47
|
+
|
48
|
+
def presentation
|
49
|
+
{ linked_resource_element_name => collection.to_a, :id => parent_id }
|
50
|
+
end
|
51
|
+
|
52
|
+
def linked_resource_element_name
|
53
|
+
"#{linked_resource_name.to_s.singularize}_ids"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Intermodal
|
2
|
+
module Proxies
|
3
|
+
module WillPaginate
|
4
|
+
module Collection
|
5
|
+
|
6
|
+
def pagination_info
|
7
|
+
{ :page => self.current_page.to_i,
|
8
|
+
:per_page => self.per_page.to_i,
|
9
|
+
:total_pages => self.total_pages,
|
10
|
+
:total_entries => self.total_entries }
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO: This needs its own spec
|
14
|
+
def as_json(*args)
|
15
|
+
options = args.extract_options!
|
16
|
+
_root = options.delete(:root)
|
17
|
+
_collection_name = _root || :collection
|
18
|
+
|
19
|
+
# Scrub out everything else
|
20
|
+
presenter_options = {
|
21
|
+
:root => (options[:always_nest_collections] && _root ? _collection_name.to_s.singularize : nil),
|
22
|
+
:always_nest_collections => options[:always_nest_collections],
|
23
|
+
:scope => options[:scope],
|
24
|
+
:presenter => options[:presenter] }
|
25
|
+
pagination_info.merge({ _collection_name => self.to_a.as_json(presenter_options)})
|
26
|
+
end
|
27
|
+
|
28
|
+
# TODO: This needs its own spec
|
29
|
+
# TODO: Should be refactored to have clearer code
|
30
|
+
def to_xml(*args)
|
31
|
+
options = args.extract_options!
|
32
|
+
_collection = self
|
33
|
+
|
34
|
+
# For a paginated collection of say, books, we want the following output:
|
35
|
+
#
|
36
|
+
# <books>
|
37
|
+
# <collection>
|
38
|
+
# <book>
|
39
|
+
# <title> ... </title>
|
40
|
+
# ...
|
41
|
+
#
|
42
|
+
# Since Rails 3.0 magically assumes that a hash
|
43
|
+
#
|
44
|
+
# { :collection => [ {:title => ' ... ' } ] }
|
45
|
+
#
|
46
|
+
# outputs to
|
47
|
+
#
|
48
|
+
# <collection>
|
49
|
+
# <collection>
|
50
|
+
# <title> ... </title>
|
51
|
+
#
|
52
|
+
# We need to use a custom builder. We use very kludgy code, by calling #to_xml
|
53
|
+
# again, but override builder so it will nest things properly.
|
54
|
+
#
|
55
|
+
# Actually, it is also depending on some magic. Presenter/API options are saved
|
56
|
+
# in the builder class, so it uses that. Which means this isn't side-effect-free
|
57
|
+
# code. Ugh.
|
58
|
+
#
|
59
|
+
# Ultimately, this needs to be more modular so that API writers can specify their
|
60
|
+
# own builder. For now, we'll dictate the auto-generated XML and wait until someone
|
61
|
+
# complains about it in the Github Issues tracker.
|
62
|
+
collection_builder = proc do |opts, key|
|
63
|
+
_builder = opts[:builder]
|
64
|
+
|
65
|
+
_builder.collection do
|
66
|
+
_collection.to_a.map do |r|
|
67
|
+
r.presentation(options.except(:root)).
|
68
|
+
to_xml(
|
69
|
+
:root => r.class.name.demodulize.underscore,
|
70
|
+
:builder => opts[:builder],
|
71
|
+
:skip_instruct => true )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Merge information such as current page, total pages, total entries, etc.
|
77
|
+
pagination_info.
|
78
|
+
merge({ :collection => collection_builder }).
|
79
|
+
to_xml(options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Intermodal
|
2
|
+
module Rack
|
3
|
+
class Auth
|
4
|
+
UNAUTHORIZED = [401, {}, []]
|
5
|
+
IDENTITY='HTTP_X_AUTH_IDENTITY'
|
6
|
+
KEY='HTTP_X_AUTH_KEY'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def valid?(env)
|
10
|
+
env[IDENTITY] && env[KEY]
|
11
|
+
end
|
12
|
+
|
13
|
+
# USAGE:
|
14
|
+
#
|
15
|
+
# (1) Define ::AccessCredential.authenticate!(identity, key) and returns an account object
|
16
|
+
# (2) Define ::AccessToken.generate!(account) and inserts token for authentication
|
17
|
+
# You can inherit from Intermodal::Auth::AccessToken
|
18
|
+
# (3) Add to routes:
|
19
|
+
# match '/auth', :to => Intermodal::Rack::Auth
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
return UNAUTHORIZED unless valid?(env) && ( account = ::AccessCredential.authenticate!(env[IDENTITY], env[KEY]))
|
23
|
+
access_token = ::AccessToken.generate!(account)
|
24
|
+
[ 204, { 'X-Auth-Token' => access_token.to_s }, []]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'action_dispatch/middleware/session/abstract_store'
|
2
|
+
|
3
|
+
module Intermodal
|
4
|
+
module Rack
|
5
|
+
# A dummy store that does nothing, but satisfies Warden's session store requirement
|
6
|
+
# This allows us to use the x_auth_token strategy
|
7
|
+
class DummyStore < ::ActionDispatch::Session::AbstractStore
|
8
|
+
|
9
|
+
def get_session(env, sid)
|
10
|
+
[nil, nil]
|
11
|
+
end
|
12
|
+
|
13
|
+
# Set a session in the cache.
|
14
|
+
def set_session(env, sid, session, options)
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Remove a session from the cache.
|
19
|
+
def destroy_session(env, sid, options)
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#require 'action_dispatch/http/mime_type'
|
2
|
+
|
3
|
+
module Intermodal
|
4
|
+
module Rack
|
5
|
+
class Rescue
|
6
|
+
include ActionController::Rescue
|
7
|
+
|
8
|
+
def self.watch(x)
|
9
|
+
ap x if defined? ap
|
10
|
+
end
|
11
|
+
|
12
|
+
def watch(x)
|
13
|
+
self.class.watch(x)
|
14
|
+
end
|
15
|
+
|
16
|
+
rescue_from Exception do |exception|
|
17
|
+
if defined? Rails and Rails.env == 'production'
|
18
|
+
[500, {}, [ "Unexpected error. Please contact support." ] ]
|
19
|
+
else
|
20
|
+
[500, {}, [ "Exception: #{exception.message}", "\n\n", exception.backtrace ].tap(&method(:watch)) ]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
rescue_from Intermodal::BadRequest do |exception|
|
25
|
+
[400, {}, [ 'Bad Request' ] ]
|
26
|
+
end
|
27
|
+
|
28
|
+
rescue_from ActiveRecord::RecordNotFound do |exception|
|
29
|
+
[404, {}, [ 'Not Found' ] ]
|
30
|
+
end
|
31
|
+
|
32
|
+
rescue_from ActionController::RoutingError do |exception|
|
33
|
+
if defined? Rails and Rails.env == 'production'
|
34
|
+
[404, {}, ['Not Found'] ]
|
35
|
+
else
|
36
|
+
[500, {}, [ "Exception: #{exception.message}", "\n", caller.join("\n"), "\n", exception.backtrace ].tap(&method(:watch)) ]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# TODO: Hack. Untested.
|
41
|
+
if defined? ActiveResource::ResourceNotFound
|
42
|
+
rescue_from ActiveResource::ResourceNotFound do |exception|
|
43
|
+
[404, {}, [ 'Not Found' ] ]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
rescue_from ActionDispatch::ParamsParser::ParseError do |exception|
|
48
|
+
[400, { 'Content-Type' => content_type(:json) }, { :parse_error => exception.message }.to_json]
|
49
|
+
end
|
50
|
+
|
51
|
+
rescue_from MultiJson::DecodeError do |exception|
|
52
|
+
[400, { 'Content-Type' => content_type(:json) }, { :parse_error => exception.message }.to_json]
|
53
|
+
end
|
54
|
+
|
55
|
+
rescue_from 'REXML::ParseException' do |exception|
|
56
|
+
[400, { 'Content-Type' => content_type(:xml) }, { :parse_error => exception.message }.to_xml]
|
57
|
+
end
|
58
|
+
|
59
|
+
def content_type(format)
|
60
|
+
"#{Mime::Type.lookup_by_extension(format)}; charset=utf-8"
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize(app, &block)
|
64
|
+
@app = app
|
65
|
+
self.class.instance_eval(&block) if block_given?
|
66
|
+
end
|
67
|
+
|
68
|
+
def call(env)
|
69
|
+
@app.call(env)
|
70
|
+
rescue Exception => exception
|
71
|
+
rescue_with_handler(exception) || raise(exception)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Overrides ActiveSuppor::Rescuable#rescue_with_handler
|
75
|
+
# All handlers *MUST* respond with a valid Rack response
|
76
|
+
def rescue_with_handler(exception)
|
77
|
+
return unless handler = handler_for_rescue(exception)
|
78
|
+
handler.arity != 0 ? handler.call(exception) : handler.call
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'action_controller/responder'
|
2
|
+
|
3
|
+
module Intermodal
|
4
|
+
class LinkingResourceResponder < ResourceResponder
|
5
|
+
|
6
|
+
# This is the common behavior for "API" requests, like :xml and :json.
|
7
|
+
def respond
|
8
|
+
if get?
|
9
|
+
display resource, :root => presentation_root
|
10
|
+
elsif has_errors?
|
11
|
+
display resource.errors, :status => :unprocessable_entity
|
12
|
+
elsif post?
|
13
|
+
display resource, :status => :created
|
14
|
+
#:location => api_location # Taken out because it requires some additional URL definitions
|
15
|
+
else
|
16
|
+
head :ok
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'action_controller/responder'
|
2
|
+
|
3
|
+
|
4
|
+
module Intermodal
|
5
|
+
class ResourceResponder < ActionController::Responder
|
6
|
+
include Intermodal::Let
|
7
|
+
|
8
|
+
attr_accessor :options
|
9
|
+
|
10
|
+
let(:presenter) { options[:presenter] || controller.send(:presenter) }
|
11
|
+
|
12
|
+
let(:presentation_root) { options[:presentation_root] || controller_send(:presentation_root) }
|
13
|
+
let(:presentation_scope) { options[:presentation_scope] || controller_send(:presentation_scope) }
|
14
|
+
let(:always_nest_collections) { options[:always_nest_collections] || controller_send(:always_nest_collections) || false }
|
15
|
+
|
16
|
+
def initialize(controller, resources, options={})
|
17
|
+
super(controller, resources, options)
|
18
|
+
@options = options
|
19
|
+
end
|
20
|
+
|
21
|
+
# This is the common behavior for "API" requests, like :xml and :json.
|
22
|
+
def respond
|
23
|
+
return head :status => 404 unless resource
|
24
|
+
if get?
|
25
|
+
display resource,
|
26
|
+
root: presentation_root,
|
27
|
+
presenter: presenter,
|
28
|
+
scope: presentation_scope,
|
29
|
+
always_nest_collections: always_nest_collections
|
30
|
+
|
31
|
+
elsif has_errors?
|
32
|
+
display resource.errors,
|
33
|
+
root: presentation_root,
|
34
|
+
status: :unprocessable_entity,
|
35
|
+
presenter: presenter,
|
36
|
+
scope: presentation_scope,
|
37
|
+
always_nest_collections: always_nest_collections
|
38
|
+
elsif put?
|
39
|
+
display resource,
|
40
|
+
root: presentation_root,
|
41
|
+
presenter: presenter,
|
42
|
+
scope: presentation_scope,
|
43
|
+
always_nest_collections: always_nest_collections
|
44
|
+
elsif post?
|
45
|
+
display resource,
|
46
|
+
root: presentation_root,
|
47
|
+
status: :created,
|
48
|
+
presenter: presenter,
|
49
|
+
scope: presentation_scope,
|
50
|
+
always_nest_collections: always_nest_collections
|
51
|
+
#:location => api_location # Taken out because it requires some additional URL definitions
|
52
|
+
else
|
53
|
+
head status: 204
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def controller_send(_method)
|
60
|
+
controller.respond_to?(_method, true) ? controller.send(_method) : nil
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Intermodal
|
2
|
+
module RSpec
|
3
|
+
module Acceptors
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
included do
|
6
|
+
extend ::Intermodal::RSpec::Macros::Acceptors
|
7
|
+
|
8
|
+
let(:resource_name) { subject.name.underscore }
|
9
|
+
let(:acceptor) { api.acceptors[resource_name.to_sym] }
|
10
|
+
let(:random_field_data) { "Accepted Field #{rand(100000)}" }
|
11
|
+
|
12
|
+
def acceptance_for(input)
|
13
|
+
acceptor.call(input.with_indifferent_access)
|
14
|
+
end
|
15
|
+
|
16
|
+
def expects_acceptance(input, expectation)
|
17
|
+
acceptance_for(input).should eql(expectation)
|
18
|
+
end
|
19
|
+
|
20
|
+
def expects_rejection(input, expectation)
|
21
|
+
acceptance_for(input).should_not eql(expectation)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Macros
|
27
|
+
module Acceptors
|
28
|
+
def concerned_with_acceptance(_model, &blk)
|
29
|
+
describe _model do
|
30
|
+
context "when concerned with acceptance" do
|
31
|
+
subject { _model }
|
32
|
+
instance_eval(&blk) if blk
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def imposes(*fields)
|
38
|
+
fields.each do |field|
|
39
|
+
it "should accept #{field}" do
|
40
|
+
expects_acceptance({ field => random_field_data }, { field.to_sym => random_field_data })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def rejects(*fields)
|
46
|
+
fields.each do |field|
|
47
|
+
it "should reject #{field}" do
|
48
|
+
expects_rejection({ field => random_field_data }, { field.to_sym => random_field_data })
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def imposes_named_resource
|
54
|
+
context 'when exposing named resource fields' do
|
55
|
+
imposes 'name'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def imposes_linked_resources(linked_resources_name, options = {})
|
60
|
+
_linked_resource_name, _linking_association = linked_resources_name.to_s.singularize, options[:with]
|
61
|
+
pending "when exposing linked resources, #{_linked_resource_name}" do
|
62
|
+
let(:linked_resource_ids_name) { :"#{_linked_resource_name}_ids" }
|
63
|
+
let(:linking_association) { :"#{_linking_association}" }
|
64
|
+
let(:linked_resource_ids) { subject.send(linking_association).send("to_#{linked_resource_ids_name}") }
|
65
|
+
|
66
|
+
it { should accept "#{_linked_resource_name}_ids" }
|
67
|
+
it "should accept \"#{_linked_resource_name}_ids\" as a collection of ids" do
|
68
|
+
resource
|
69
|
+
(presenter[linked_resource_ids_name].to_a & linked_resource_ids.to_a).should be_empty
|
70
|
+
end
|
71
|
+
|
72
|
+
pending "should only accept \"#{_linked_resource_name}_ids\" scoped to account"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|