# Abstract controller providing basic CRUD actions. # This implementation mainly follows the one of the Rails scaffolding # controller and responses to HTML and JSON requests. Some enhancements were made to ease extendability. # Several protected helper methods are there to be (optionally) overriden by subclasses. # With the help of additional callbacks, it is possible to hook into the action procedures without # overriding the entire method. class CrudController < ListController include ERB::Util # Set up entry object to use in the various actions. before_filter :build_entry, :only => [:new, :create] before_filter :set_entry, :only => [:show, :edit, :update, :destroy] helper_method :full_entry_label delegate :model_identifier, :to => 'self.class' hide_action :model_identifier, :run_callbacks # Defines before and after callback hooks for create, update, save and destroy actions. define_model_callbacks :create, :update, :save, :destroy # Defines before callbacks for the render actions. A virtual callback # unifiying render_new and render_edit, called render_form, is defined further down. define_render_callbacks :show, :new, :edit ############## ACTIONS ############################################ # Show one entry of this model. # GET /entries/1 # GET /entries/1.json def show respond_with @entry end # Display a form to create a new entry of this model. # GET /entries/new # GET /entries/new.json def new @entry.attributes = params[model_identifier] respond_with @entry end # Create a new entry of this model from the passed params. # There are before and after create callbacks to hook into the action. # To customize the response, you may overwrite this action and call # super with a block that gets success and format parameters. # POST /entries # POST /entries.json def create(&block) @entry.attributes = params[model_identifier] created = with_callbacks(:create, :save) { @entry.save } customizable_respond_to(created, block) do |format| if created format.html { redirect_to_show success_notice } format.json { render :json => @entry, :status => :created, :location => @entry } else format.html { render_with_callback 'new' } format.json { render :json => @entry.errors, :status => :unprocessable_entity } end end end # Display a form to edit an exisiting entry of this model. # GET /entries/1/edit def edit render_with_callback 'edit' end # Update an existing entry of this model from the passed params. # There are before and after update callbacks to hook into the action. # To customize the response, you may overwrite this action and call # super with a block that gets success and format parameters. # PUT /entries/1 # PUT /entries/1.json def update(&block) @entry.attributes = params[model_identifier] updated = with_callbacks(:update, :save) { @entry.save } customizable_respond_to(updated, block) do |format| if updated format.html { redirect_to_show success_notice } format.json { head :ok } else format.html { render_with_callback 'edit' } format.json { render :json => @entry.errors, :status => :unprocessable_entity } end end end # Destroy an existing entry of this model. # There are before and after destroy callbacks to hook into the action. # To customize the response, you may overwrite this action and call # super with a block that gets success and format parameters. # DELETE /entries/1 # DELETE /entries/1.json def destroy(&block) destroyed = run_callbacks(:destroy) { @entry.destroy } customizable_respond_to(destroyed, block) do |format| if destroyed format.html { redirect_to_index success_notice } format.json { head :ok } else format.html { flash.alert = @entry.errors.full_messages.join('
') request.env["HTTP_REFERER"].present? ? redirect_to(:back) : redirect_to_show } format.json { render :json => @entry.errors, :status => :unprocessable_entity } end end end protected ############# CUSTOMIZABLE HELPER METHODS ############################## # Creates a new model entry. def build_entry @entry = model_class.new end # Sets an existing model entry from the given id. def set_entry @entry = model_class.find(params[:id]) end # A label for the current entry, including the model name. def full_entry_label "#{models_label(false)} #{h(@entry)}".html_safe end # Redirects to the show action of a single entry. def redirect_to_show(options = {}) redirect_to @entry, options end # Redirects to the main action of this controller. def redirect_to_index(options = {}) redirect_to polymorphic_path(model_class, :returning => true), options end # Helper method the run the given block in between the before and after # callbacks of the given kinds. def with_callbacks(*kinds, &block) kinds.reverse.inject(block) do |b, kind| lambda { run_callbacks(kind, &b) } end.call end private # Convenience method to respond to various formats if the performed # action may succeed or fail. It is possible to pass a custom_block and respond # in custom ways for certain cases. If no response is performed in the # given block, the default responses in the main block are executed. def customizable_respond_to(success, custom_block = nil) respond_to do |format| custom_block.call(success, format) if custom_block return if performed? yield format end end # Create an I18n flash notice if the action was successfull. # Uses the key {controller_name}.{action_name}.flash.success # or crud.{action_name}.flash.success as fallback. def success_notice key = "#{action_name}.flash.success" {:notice => t(:"#{controller_name}.#{key}", :model => full_entry_label, :default => :"crud.#{key}")} end class << self # The identifier of the model used for form parameters. # I.e., the symbol of the underscored model name. def model_identifier @model_identifier ||= model_class.name.underscore.to_sym end # Convenience callback to apply a callback on both form actions (new and edit). def before_render_form(*methods) before_render_new *methods before_render_edit *methods end end end