|
module RestfulSupport
|
|
class ResourceNotFound < RuntimeError
|
|
end
|
|
|
|
class UnrelatedResources < RuntimeError
|
|
end
|
|
end
|
|
|
|
module RestfulSupport::Common
|
|
# suitable array of objects for polymorphic_path method
|
|
def resource_family(obj = nil)
|
|
# dup because polymorphic_path seems to eat the stuff
|
|
z = resource_namespace_components + @parent_resource_objects.dup
|
|
unless obj.nil?
|
|
if obj.is_a? Array
|
|
z += obj
|
|
else
|
|
z.push(obj)
|
|
end
|
|
end
|
|
return z.first if z.size == 1
|
|
return z
|
|
end
|
|
|
|
def parent_resource_path
|
|
polymorphic_path(resource_family)
|
|
end
|
|
|
|
def parent_resource_index_path
|
|
index_polymorphic_path(resource_family)
|
|
end
|
|
|
|
def resource_path(obj)
|
|
polymorphic_path(resource_family(obj))
|
|
end
|
|
|
|
def resource_index_path
|
|
index_polymorphic_path(resource_family(resource_model.new))
|
|
end
|
|
|
|
def index_polymorphic_path(params)
|
|
params = [params] unless params.is_a? Array
|
|
l_param = params.pop
|
|
if l_param.is_a? Class
|
|
klass = l_param
|
|
else
|
|
klass = l_param.class
|
|
end
|
|
params.push(klass.new)
|
|
# using polymorphic_path on a new object send back the path to the collection
|
|
polymorphic_path(params)
|
|
end
|
|
|
|
def child_polymorphic_path(obj, child)
|
|
polymorphic_path(resource_family([obj, child]))
|
|
end
|
|
|
|
def child_index_polymorphic_path(obj, model)
|
|
index_polymorphic_path(resource_family([obj, model.new]))
|
|
end
|
|
|
|
def resources_name
|
|
controller_name
|
|
end
|
|
|
|
def resource_namespace_components
|
|
list = controller_path.split("/")
|
|
list.pop
|
|
list.collect {|pc| pc.to_sym}
|
|
end
|
|
|
|
def convert_resources_name_to_resource_name(name)
|
|
name.singularize
|
|
end
|
|
|
|
def resource_name
|
|
convert_resources_name_to_resource_name(resources_name)
|
|
end
|
|
|
|
def resource_sym
|
|
resource_name.to_sym
|
|
end
|
|
|
|
def convert_resources_name_to_resource_model(name)
|
|
convert_resources_name_to_resource_name(name).camelize.constantize
|
|
end
|
|
|
|
def resource_model
|
|
convert_resources_name_to_resource_model(resources_name)
|
|
end
|
|
|
|
def convert_resources_name_to_resource_variable_sym(name)
|
|
("@" + convert_resources_name_to_resource_name(name)).to_sym
|
|
end
|
|
|
|
def convert_resource_model_to_resource_name(model)
|
|
model.to_s.underscore
|
|
end
|
|
|
|
def discover_associated_model_method(src_model, dst_model)
|
|
# singular method name
|
|
var_sym = convert_resource_model_to_resource_name(dst_model).to_sym
|
|
return var_sym if src_model.new.respond_to?(var_sym)
|
|
# plural method name
|
|
var_sym = var_sym.to_s.pluralize.to_sym
|
|
return var_sym if src_model.new.respond_to?(var_sym)
|
|
return nil
|
|
end
|
|
|
|
def resource_object
|
|
instance_variable_get(convert_resources_name_to_resource_variable_sym(resources_name))
|
|
end
|
|
end
|
|
|
|
module RestfulSupport::ActionView
|
|
def controller_name
|
|
@controller.controller_name
|
|
end
|
|
|
|
def controller_path
|
|
@controller.controller_path
|
|
end
|
|
|
|
def form_for_resource(*args, &block)
|
|
form_for resource_family(resource_object), *args, &block
|
|
end
|
|
|
|
def error_messages_for_resource
|
|
error_messages_for resource_sym
|
|
end
|
|
end
|
|
|
|
module RestfulSupport::ActionController
|
|
# implement a simple rest support for a resource
|
|
# the models are the class names for the associated DB objects, last being the model to be manipulated in this controller
|
|
# TODO: use resource instead of model, using 'camelize' method for String
|
|
def simple_rest_support(models = [])
|
|
class_eval do
|
|
include RestfulSupport::Common
|
|
helper_method RestfulSupport::Common.instance_methods
|
|
|
|
include RestfulSupport::ActionController::InstanceMethods
|
|
|
|
cattr_accessor :srs_models
|
|
|
|
prepend_before_filter :initialize_view
|
|
|
|
def initialize_view
|
|
unless @template.respond_to? :resource_family
|
|
@template.extend(RestfulSupport::ActionView)
|
|
end
|
|
end
|
|
end
|
|
|
|
srs_models = (models.kind_of? Array) ? models : [models]
|
|
srs_models << convert_resources_name_to_resource_model(controller_name)
|
|
class_variable_set(:@@srs_models, srs_models)
|
|
|
|
if srs_models.last.new.respond_to? :acts_as_list_class
|
|
class_eval do
|
|
include RestfulSupport::ActionController::InstanceMethods::ActsAsList
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# how can i reach @template to extend it with the module instead of ApplicationHelper ?
|
|
#ApplicationHelper.module_eval('include RestfulSupport::Common')
|
|
|
|
module RestfulSupport::ActionController::InstanceMethods
|
|
def self.included(base)
|
|
base.extend ClassMethods
|
|
|
|
base.class_eval do
|
|
before_filter :load_objects, :only => [:index, :new, :edit, :show, :create, :update, :destroy, :move]
|
|
end
|
|
end
|
|
|
|
# helpers
|
|
|
|
def new_resource_object
|
|
instance_variable_set(convert_resources_name_to_resource_variable_sym(resources_name), resource_model.new)
|
|
end
|
|
|
|
def resource_params
|
|
params[resource_sym]
|
|
end
|
|
|
|
# filters
|
|
|
|
def load_objects
|
|
action = action_name.to_sym
|
|
|
|
ancestors_only = [:index, :new, :create].include?(action)
|
|
find_model(ancestors_only)
|
|
|
|
new_model() if [:new, :create].include?(action)
|
|
end
|
|
|
|
# listing request
|
|
|
|
def index
|
|
end
|
|
|
|
# forms request
|
|
|
|
def new
|
|
obj = resource_object
|
|
|
|
respond_to do |format|
|
|
format.html # new.rhtml
|
|
format.xml { render :xml => obj.dup }
|
|
end
|
|
end
|
|
|
|
def edit
|
|
obj = resource_object
|
|
|
|
respond_to do |format|
|
|
format.html # edit.rhtml
|
|
format.xml { render :xml => prepared_object(obj.dup) }
|
|
end
|
|
end
|
|
|
|
# actions
|
|
|
|
def show
|
|
obj = resource_object
|
|
|
|
respond_to do |format|
|
|
format.html # show.rhtml
|
|
format.xml { render :xml => prepared_object(obj.dup) }
|
|
end
|
|
end
|
|
|
|
def create
|
|
obj = resource_object
|
|
obj.attributes=(prepared_params)
|
|
unless @parent_resource_objects.empty?
|
|
parent = @parent_resource_objects.last
|
|
method = (convert_resource_model_to_resource_name(parent.class) + "_id=").to_sym
|
|
obj.send(method, parent.id) if obj.respond_to?(method)
|
|
end
|
|
res = resource_family(obj)
|
|
|
|
respond_to do |format|
|
|
if obj.save
|
|
flash[:notice] = sprintf(_("%s successfully created."), _(obj.human_name).capitalize)
|
|
# If we're in HTML mode, redirect back to the master list.
|
|
format.html { redirect_to(index_polymorphic_path(res.dup)) }
|
|
# If we're in XML mode, just return a 201 Created response.
|
|
format.xml { head :created, :location => polymorphic_path(res.dup) }
|
|
else
|
|
format.html { render :action => "new" }
|
|
format.xml { render :xml => obj.errors, :status => :unprocessable_entity }
|
|
end
|
|
end
|
|
end
|
|
|
|
def update
|
|
obj = resource_object
|
|
obj.attributes=(prepared_params)
|
|
res = resource_family(obj)
|
|
|
|
respond_to do |format|
|
|
if obj.save
|
|
flash[:notice] = sprintf(_("%s successfully modified"), _(obj.human_name).capitalize)
|
|
# If we're in HTML mode, redirect back to the master list.
|
|
format.html { redirect_to(index_polymorphic_path(res.dup)) }
|
|
# If we're in XML mode, just return a 201 Created response.
|
|
format.xml { head :ok }
|
|
else
|
|
format.html { render :action => "edit" }
|
|
format.xml { render :xml => obj.errors, :status => :unprocessable_entity }
|
|
end
|
|
end
|
|
end
|
|
|
|
def destroy
|
|
obj = resource_object
|
|
obj.destroy
|
|
res = resource_family(obj)
|
|
|
|
respond_to do |format|
|
|
flash[:notice] = sprintf(_("%s successfully deleted"), _(obj.human_name).capitalize)
|
|
format.html { redirect_to(index_polymorphic_path(res.dup)) }
|
|
format.xml { head :ok }
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def new_model
|
|
new_resource_object
|
|
end
|
|
|
|
def find_model(ancestors_only = false)
|
|
srs_find_resources(ancestors_only)
|
|
return resource_object unless ancestors_only
|
|
rescue ActiveRecord::RecordNotFound
|
|
raise RestfulSupport::ResourceNotFound
|
|
end
|
|
|
|
def srs_find_resources(ancestors_only = false)
|
|
list = []
|
|
previous_obj = nil
|
|
self.class.srs_models.each do |model|
|
|
# skip current model if asked for ancestors only (probably because there is not yet a current model)
|
|
next if ancestors_only and model == resource_model
|
|
|
|
# fetch parent advertised via routing
|
|
key = (model == resource_model) ? :id : model.to_s.foreign_key.to_sym
|
|
obj = model.find(params[key])
|
|
|
|
# check if this is a valid parent
|
|
unless previous_obj.nil?
|
|
res_name = convert_resource_model_to_resource_name(model)
|
|
if previous_obj.respond_to? res_name.to_sym
|
|
raise RestfulSupport::UnrelatedResources unless previous_obj.send(res_name.to_sym) == obj
|
|
elsif previous_obj.respond_to? res_name.pluralize.to_sym
|
|
raise RestfulSupport::UnrelatedResources unless previous_obj.send(res_name.pluralize.to_sym).include?(obj)
|
|
else
|
|
raise RestfulSupport::UnrelatedResources
|
|
end
|
|
end
|
|
|
|
# create instance variable for easy access
|
|
var_sym = ("@" + convert_resource_model_to_resource_name(model)).to_sym
|
|
instance_variable_set(var_sym, obj)
|
|
|
|
# save ancestor
|
|
previous_obj = obj
|
|
list << obj unless model == resource_model
|
|
end
|
|
@parent_resource_objects = list
|
|
end
|
|
|
|
def prepared_params
|
|
resource_params
|
|
end
|
|
|
|
def prepared_object(obj)
|
|
obj
|
|
end
|
|
|
|
module ClassMethods
|
|
def convert_resources_name_to_resource_name(name)
|
|
name.singularize
|
|
end
|
|
|
|
def convert_resources_name_to_resource_model(name)
|
|
convert_resources_name_to_resource_name(name).camelize.constantize
|
|
end
|
|
|
|
def convert_resources_name_to_resource_variable_sym(name)
|
|
("@" + convert_resources_name_to_resource_name(name)).to_sym
|
|
end
|
|
|
|
def convert_resource_model_to_resource_name(model)
|
|
model.to_s.underscore
|
|
end
|
|
end
|
|
|
|
module ActsAsList
|
|
def move
|
|
obj = resource_object
|
|
res = resource_family(obj)
|
|
|
|
type = params[:type]
|
|
type_ok = false
|
|
if ["move_lower", "move_higher", "move_to_top", "move_to_bottom"].include?(type)
|
|
type_ok = true
|
|
obj.send(type)
|
|
end
|
|
|
|
respond_to do |format|
|
|
if type_ok
|
|
flash[:notice] = sprintf(_("%s successfully moved"), _(obj.human_name).capitalize)
|
|
format.html { redirect_to(index_polymorphic_path(res.dup)) }
|
|
format.xml { head :ok }
|
|
else
|
|
format.html { redirect_to(index_polymorphic_path(res.dup)) }
|
|
format.xml { render :xml => obj.errors, :status => :not_found }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|