resource_mapper 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +23 -0
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +84 -0
- data/VERSION +1 -0
- data/examples/example_helper.rb +42 -0
- data/examples/lib/sinatra/resource_example.rb +150 -0
- data/examples/models.rb +77 -0
- data/lib/resource_mapper.rb +31 -0
- data/lib/resource_mapper/accessors.rb +77 -0
- data/lib/resource_mapper/action_options.rb +35 -0
- data/lib/resource_mapper/actions.rb +56 -0
- data/lib/resource_mapper/class_methods.rb +65 -0
- data/lib/resource_mapper/controller.rb +56 -0
- data/lib/resource_mapper/failable_action_options.rb +25 -0
- data/lib/resource_mapper/helpers/current_objects.rb +82 -0
- data/lib/resource_mapper/helpers/internal.rb +58 -0
- data/lib/resource_mapper/helpers/nested.rb +67 -0
- data/lib/resource_mapper/response_collector.rb +27 -0
- data/lib/sinatra/resource.rb +104 -0
- data/resource_mapper.gemspec +80 -0
- metadata +170 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'active_support'
|
3
|
+
require 'mongo_mapper'
|
4
|
+
|
5
|
+
module ResourceMapper
|
6
|
+
ACTIONS = [:index, :show, :create, :update, :destroy]
|
7
|
+
SINGLETON_ACTIONS = (ACTIONS - [:index]).freeze
|
8
|
+
FAILABLE_ACTIONS = (ACTIONS - [:index]).freeze
|
9
|
+
NAME_ACCESSORS = [:model_name, :route_name, :object_name]
|
10
|
+
|
11
|
+
autoload :Accessors, 'resource_mapper/accessors'
|
12
|
+
autoload :ActionOptions, 'resource_mapper/action_options'
|
13
|
+
autoload :Actions, 'resource_mapper/actions'
|
14
|
+
autoload :ClassMethods, 'resource_mapper/class_methods'
|
15
|
+
autoload :Controller, 'resource_mapper/controller'
|
16
|
+
autoload :FailableActionOptions, 'resource_mapper/failable_action_options'
|
17
|
+
autoload :ResponseCollector, 'resource_mapper/response_collector'
|
18
|
+
|
19
|
+
module Helpers
|
20
|
+
autoload :Internal, 'resource_mapper/helpers/internal'
|
21
|
+
autoload :Nested, 'resource_mapper/helpers/nested'
|
22
|
+
autoload :CurrentObjects, 'resource_mapper/helpers/current_objects'
|
23
|
+
|
24
|
+
include ResourceMapper::Helpers::Internal
|
25
|
+
include ResourceMapper::Helpers::Nested
|
26
|
+
include ResourceMapper::Helpers::CurrentObjects
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'sinatra/resource'
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module ResourceMapper # :nodoc:
|
2
|
+
module Accessors # :nodoc:
|
3
|
+
private
|
4
|
+
def block_accessor(*accessors)
|
5
|
+
accessors.each do |block_accessor|
|
6
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
7
|
+
|
8
|
+
def #{block_accessor}(*args, &block)
|
9
|
+
unless args.empty? && block.nil?
|
10
|
+
args.push block if block_given?
|
11
|
+
@#{block_accessor} = [args].flatten
|
12
|
+
end
|
13
|
+
|
14
|
+
@#{block_accessor}
|
15
|
+
end
|
16
|
+
|
17
|
+
end_eval
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def scoping_reader(*accessor_names)
|
22
|
+
accessor_names.each do |accessor_name|
|
23
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
24
|
+
def #{accessor_name}(&block)
|
25
|
+
@#{accessor_name}.instance_eval &block if block_given?
|
26
|
+
@#{accessor_name}
|
27
|
+
end
|
28
|
+
end_eval
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def class_scoping_reader(accessor_name, start_value)
|
33
|
+
write_inheritable_attribute accessor_name, start_value
|
34
|
+
|
35
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
36
|
+
def self.#{accessor_name}(&block)
|
37
|
+
read_inheritable_attribute(:#{accessor_name}).instance_eval(&block) if block_given?
|
38
|
+
read_inheritable_attribute(:#{accessor_name})
|
39
|
+
end
|
40
|
+
end_eval
|
41
|
+
end
|
42
|
+
|
43
|
+
def reader_writer(accessor_name)
|
44
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
45
|
+
def #{accessor_name}(*args, &block)
|
46
|
+
args << block unless block.nil?
|
47
|
+
@#{accessor_name} = args.first unless args.empty?
|
48
|
+
@#{accessor_name}
|
49
|
+
end
|
50
|
+
end_eval
|
51
|
+
end
|
52
|
+
|
53
|
+
def class_reader_writer(*accessor_names)
|
54
|
+
accessor_names.each do |accessor_name|
|
55
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
56
|
+
def self.#{accessor_name}(*args)
|
57
|
+
unless args.empty?
|
58
|
+
write_inheritable_attribute :#{accessor_name}, args.first if args.length == 1
|
59
|
+
write_inheritable_attribute :#{accessor_name}, args if args.length > 1
|
60
|
+
end
|
61
|
+
|
62
|
+
read_inheritable_attribute :#{accessor_name}
|
63
|
+
end
|
64
|
+
|
65
|
+
def #{accessor_name}(*args)
|
66
|
+
unless args.empty?
|
67
|
+
self.class.write_inheritable_attribute :#{accessor_name}, args.first if args.length == 1
|
68
|
+
self.class.write_inheritable_attribute :#{accessor_name}, args if args.length > 1
|
69
|
+
end
|
70
|
+
|
71
|
+
self.class.read_inheritable_attribute :#{accessor_name}
|
72
|
+
end
|
73
|
+
end_eval
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ResourceMapper
|
2
|
+
class ActionOptions
|
3
|
+
extend ResourceMapper::Accessors
|
4
|
+
|
5
|
+
block_accessor :after, :before
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@collector = ResourceMapper::ResponseCollector.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def response(*args, &block)
|
12
|
+
if !args.empty? || block_given?
|
13
|
+
@collector.clear
|
14
|
+
args.flatten.each { |symbol| @collector.send(symbol) }
|
15
|
+
block.call(@collector) if block_given?
|
16
|
+
end
|
17
|
+
|
18
|
+
@collector.responses
|
19
|
+
end
|
20
|
+
alias_method :respond_to, :response
|
21
|
+
alias_method :responds_to, :response
|
22
|
+
|
23
|
+
def wants
|
24
|
+
@collector
|
25
|
+
end
|
26
|
+
|
27
|
+
def dup
|
28
|
+
returning self.class.new do |duplicate|
|
29
|
+
duplicate.instance_variable_set(:@collector, wants.dup)
|
30
|
+
duplicate.instance_variable_set(:@before, before.dup) unless before.nil?
|
31
|
+
duplicate.instance_variable_set(:@after, after.dup) unless after.nil?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ResourceMapper
|
2
|
+
module Actions
|
3
|
+
|
4
|
+
def index
|
5
|
+
load_collection
|
6
|
+
before :index
|
7
|
+
response_for :index
|
8
|
+
end
|
9
|
+
|
10
|
+
def show
|
11
|
+
load_object
|
12
|
+
before :show
|
13
|
+
response_for :show
|
14
|
+
rescue #ActiveRecord::RecordNotFound
|
15
|
+
response_for :show_fails
|
16
|
+
end
|
17
|
+
|
18
|
+
def create
|
19
|
+
build_object
|
20
|
+
load_object
|
21
|
+
before :create
|
22
|
+
if object.save
|
23
|
+
after :create
|
24
|
+
response_for :create
|
25
|
+
else
|
26
|
+
after :create_fails
|
27
|
+
response_for :create_fails
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def update
|
32
|
+
load_object
|
33
|
+
before :update
|
34
|
+
if object.update_attributes object_params
|
35
|
+
after :update
|
36
|
+
response_for :update
|
37
|
+
else
|
38
|
+
after :update_fails
|
39
|
+
response_for :update_fails
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def destroy
|
44
|
+
load_object
|
45
|
+
before :destroy
|
46
|
+
if object.destroy
|
47
|
+
after :destroy
|
48
|
+
response_for :destroy
|
49
|
+
else
|
50
|
+
after :destroy_fails
|
51
|
+
response_for :destroy_fails
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ResourceMapper
|
2
|
+
module ClassMethods
|
3
|
+
|
4
|
+
# Use this method in your controller to specify which actions you'd like it to respond to.
|
5
|
+
#
|
6
|
+
# class PostsController < ResourceController::Base
|
7
|
+
# actions :all, :except => :create
|
8
|
+
# end
|
9
|
+
def actions(*opts)
|
10
|
+
config = {}
|
11
|
+
config.merge!(opts.pop) if opts.last.is_a?(Hash)
|
12
|
+
|
13
|
+
all_actions = (singleton? ? ResourceMapper::SINGLETON_ACTIONS : ResourceMapper::ACTIONS)
|
14
|
+
|
15
|
+
actions_to_remove = []
|
16
|
+
actions_to_remove += all_actions - opts unless opts.first == :all
|
17
|
+
actions_to_remove += [*config[:except]] if config[:except]
|
18
|
+
actions_to_remove.uniq!
|
19
|
+
|
20
|
+
actions_to_remove.each { |action| undef_method(action) if method_defined?(action) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def key(sym)
|
24
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
25
|
+
def key
|
26
|
+
:#{sym}
|
27
|
+
end
|
28
|
+
end_eval
|
29
|
+
end
|
30
|
+
|
31
|
+
def before(*args, &block)
|
32
|
+
raise "Need to specify a block for a before set." unless block_given?
|
33
|
+
|
34
|
+
args.flatten.each do |arg|
|
35
|
+
self.method(arg).call.before &block
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO: After fail after success
|
40
|
+
def after(*args, &block)
|
41
|
+
raise "Need to specify a block for an after set." unless block_given?
|
42
|
+
|
43
|
+
args.flatten.each do |arg|
|
44
|
+
self.method(arg).call.after &block
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def read_params(*args)
|
49
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
50
|
+
def read_params
|
51
|
+
{:only => #{args}}
|
52
|
+
end
|
53
|
+
end_eval
|
54
|
+
end
|
55
|
+
|
56
|
+
def write_params(*args)
|
57
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
58
|
+
def write_params
|
59
|
+
#{args}
|
60
|
+
end
|
61
|
+
end_eval
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ResourceMapper
|
2
|
+
module Controller
|
3
|
+
def self.included(subclass)
|
4
|
+
subclass.class_eval do
|
5
|
+
include ResourceMapper::Helpers
|
6
|
+
include ResourceMapper::Actions
|
7
|
+
extend ResourceMapper::Accessors
|
8
|
+
extend ResourceMapper::ClassMethods
|
9
|
+
|
10
|
+
class_reader_writer :belongs_to, *NAME_ACCESSORS
|
11
|
+
NAME_ACCESSORS.each { |accessor| send(accessor, controller_name.singularize.underscore) }
|
12
|
+
|
13
|
+
ACTIONS.each do |action|
|
14
|
+
class_scoping_reader action, FAILABLE_ACTIONS.include?(action) ? ResourceMapper::FailableActionOptions.new : ResourceMapper::ActionOptions.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
init_default_actions(subclass)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def self.init_default_actions(klass)
|
23
|
+
klass.class_eval do
|
24
|
+
index.wants.json { collection.to_json(read_params) }
|
25
|
+
|
26
|
+
show do
|
27
|
+
wants.json { object.to_json(read_params) }
|
28
|
+
failure.wants.json {
|
29
|
+
throw(:halt, [404, 'Not found.'])
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
create do
|
34
|
+
wants.json { object.to_json(read_params) }
|
35
|
+
failure.wants.json { "BOOOM!!!" }
|
36
|
+
end
|
37
|
+
|
38
|
+
update do
|
39
|
+
wants.json { object.to_json(read_params) }
|
40
|
+
failure.wants.json { "BOOOM!!!" }
|
41
|
+
end
|
42
|
+
|
43
|
+
destroy do
|
44
|
+
wants.json { "" }
|
45
|
+
failure.wants.json { "BOOM!!!" }
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
def singleton?
|
50
|
+
false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ResourceMapper
|
2
|
+
class FailableActionOptions
|
3
|
+
extend ResourceMapper::Accessors
|
4
|
+
|
5
|
+
scoping_reader :success, :fails
|
6
|
+
alias_method :failure, :fails
|
7
|
+
|
8
|
+
block_accessor :before
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@success = ActionOptions.new
|
12
|
+
@fails = ActionOptions.new
|
13
|
+
end
|
14
|
+
|
15
|
+
delegate :after, :response, :wants, :to => :success
|
16
|
+
|
17
|
+
def dup
|
18
|
+
returning self.class.new do |duplicate|
|
19
|
+
duplicate.instance_variable_set(:@success, success.dup)
|
20
|
+
duplicate.instance_variable_set(:@fails, fails.dup)
|
21
|
+
duplicate.instance_variable_set(:@before, before.dup) unless before.nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module ResourceMapper
|
2
|
+
module Helpers
|
3
|
+
module CurrentObjects
|
4
|
+
protected
|
5
|
+
# Used internally to return the model for your resource.
|
6
|
+
#
|
7
|
+
def model
|
8
|
+
model_name.to_s.camelize.constantize
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
# Used to fetch the collection for the index method
|
13
|
+
#
|
14
|
+
# In order to customize the way the collection is fetched, to add something like pagination, for example, override this method.
|
15
|
+
#
|
16
|
+
def collection
|
17
|
+
end_of_association_chain.all
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the current param for key.
|
21
|
+
#
|
22
|
+
# Override this method if you'd like to use an alternate param name.
|
23
|
+
#
|
24
|
+
def param
|
25
|
+
params[:id]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the key used for finding.
|
29
|
+
def key
|
30
|
+
:id
|
31
|
+
end
|
32
|
+
|
33
|
+
# Used to fetch the current member object in all of the singular methods that operate on an existing member.
|
34
|
+
#
|
35
|
+
# Override this method if you'd like to fetch your objects in some alternate way, like using a permalink.
|
36
|
+
#
|
37
|
+
# class PostsController < ResourceController::Base
|
38
|
+
# private
|
39
|
+
# def object
|
40
|
+
# @object ||= end_of_association_chain.find_by_permalink(param)
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
def object
|
45
|
+
@object ||= end_of_association_chain.first({key => param}) unless param.nil?
|
46
|
+
@object
|
47
|
+
end
|
48
|
+
|
49
|
+
# Used internally to load the member object in to an instance variable @#{model_name} (i.e. @post)
|
50
|
+
#
|
51
|
+
def load_object
|
52
|
+
instance_variable_set "@#{parent_type}", parent_object if parent?
|
53
|
+
instance_variable_set "@#{object_name}", object
|
54
|
+
end
|
55
|
+
|
56
|
+
# Used internally to load the collection in to an instance variable @#{model_name.pluralize} (i.e. @posts)
|
57
|
+
#
|
58
|
+
def load_collection
|
59
|
+
instance_variable_set "@#{parent_type}", parent_object if parent?
|
60
|
+
instance_variable_set "@#{object_name.to_s.pluralize}", collection
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the form params. Defaults to params[model_name] (i.e. params["post"])
|
64
|
+
#
|
65
|
+
def object_params
|
66
|
+
ret = params["#{object_name}"]
|
67
|
+
ret.delete_if { |k, v| write_params.index(k.to_sym).nil? } unless write_params.nil?
|
68
|
+
ret
|
69
|
+
end
|
70
|
+
|
71
|
+
# Builds the object, but doesn't save it, during the new, and create action.
|
72
|
+
#
|
73
|
+
def build_object
|
74
|
+
@object = end_of_association_chain.send parent? ? :build : :new, object_params
|
75
|
+
end
|
76
|
+
|
77
|
+
def read_params ; nil end
|
78
|
+
def write_params ; nil end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Internal action lifecycle management.
|
2
|
+
#
|
3
|
+
# All of these methods are used internally to execute the options, set by the user in ActionOptions and FailableActionOptions
|
4
|
+
#
|
5
|
+
module ResourceMapper
|
6
|
+
module Helpers
|
7
|
+
module Internal
|
8
|
+
protected
|
9
|
+
# Used to actually pass the responses along to the controller's respond_to method.
|
10
|
+
#
|
11
|
+
def response_for(action)
|
12
|
+
respond_to do |wants|
|
13
|
+
options_for(action).response.each do |method, block|
|
14
|
+
if block.nil?
|
15
|
+
wants.send(method)
|
16
|
+
else
|
17
|
+
wants.send(method) { instance_eval(&block) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Calls the after callbacks for the action, if one is present.
|
24
|
+
#
|
25
|
+
def after(action)
|
26
|
+
invoke_callbacks *options_for(action).after
|
27
|
+
end
|
28
|
+
|
29
|
+
# Calls the before block for the action, if one is present.
|
30
|
+
#
|
31
|
+
def before(action)
|
32
|
+
invoke_callbacks *self.class.send(action).before
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the options for an action, which is a symbol.
|
36
|
+
#
|
37
|
+
# Manages splitting things like :create_fails.
|
38
|
+
#
|
39
|
+
def options_for(action)
|
40
|
+
action = "#{action}".split('_').map(&:to_sym)
|
41
|
+
options = self.class.send(action.first)
|
42
|
+
options = options.send(action.last == :fails ? :fails : :success) if ResourceMapper::FAILABLE_ACTIONS.include? action.first
|
43
|
+
|
44
|
+
options
|
45
|
+
end
|
46
|
+
|
47
|
+
def invoke_callbacks(*callbacks)
|
48
|
+
unless callbacks.empty?
|
49
|
+
callbacks.select { |callback| callback.is_a? Symbol }.each { |symbol| send(symbol) }
|
50
|
+
|
51
|
+
block = callbacks.detect { |callback| callback.is_a? Proc }
|
52
|
+
instance_eval &block unless block.nil?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|