Before_filters on re-rendered methods - what is the correct RAILS way?

Hello,

My Question:

How can one have the equivalent of “before_filter” checking (e.g. to
require a user to be logged in before carrying out an action) on a
method when the method is called implicitly inside another action (the
before_filter doesn’t seem to get run)?

Background:

So in this instance I have a restful controller that allows users to
enter a new “entry”, but after they click the next button instead of
automatically creating the new entry, I would like to provide an
additional “confirmation” stage where they can confirm their entry
details, and their entries can be validated as well. After they have
confirmed and the entries validated, then I would like to call the
create action where it is then created.

My understanding is that you can’t redirect a post request to another
handler (without writing a separate ruby method to create a HTTP
request that resends the POST parameters etc…) and so what I have
read somewhere else is to do if/then/else checking with params of the
submit button’s name instead.

Unfortunately doing it this way (refer to code below), the
before_filter which requires a user to be logged in, doesn’t get run.
What I would like to do is place no restrictions up to the
confirmation stage, and only require a logged in user for the create
action.

Is there a right way to do this or is this just a really un-rails way
of doing this - in which case how should it be done normally?
Thanks for your help!

Sample Code (Rails v2.3.4):

class EntryController < ApplicationController
before_filter :require_user, :only => [:create]

# ==new
# new_entry_url (GET /entries/new)
def new
   # Display user a form page where they can fill
   # in details of a new entry, which will then
   # submit a post request to "confirm"
end


# ==confirm
# confirm_new_entry_url (POST /entries/new/confirm)
def confirm

   # Check params for button type so we can still handle it
   # as a "post" request without redirection

   if params["next"]
      # Validate entry and present user with a confirmation page
      # so they can check their entries are correct _before_
      # entry is created.

   elsif params["create"]
      # Called from the confirmation page where there will
      # be a button with name "create"
      create
      render :action => :create
      return

   elsif params["back"]
      # Called from the confirmation page where there will
      # be a button with name "back" to correct user's entries
      new
      render :action => :new
      return
end


# ==create
# entries_url (POST /entries)
def create

   # This action should only be run if the user is logged in!
   # But because of the re-rendering from confirm action,
   # the before_filter that checks whether user is logged in or
   # not does not seem to get run.
   #
   # @entry.save etc...

end

end

Thanks,
Chris

render :action => :create only renders the template for the create
action, it doesnt run the actual action, thats why the before filter
doesnt get called.
Rendering actions and rendering templates are very similar. So simliar
that Im not sure why they arent merged into one.

Why are you going with one controller action and the if/elsif/else
solution instead of just separating steps into different controller
actions?

Hello,

render :action => :create only renders the template for the create

action, it doesnt run the actual action, thats why the before filter
doesnt get called.

But if you look at the sample code, I did try to call the “Create”
method as
well as rendering its template:

 elsif params["create"]
     # Called from the confirmation page where there will
     # be a button with name "create"
     create
     render :action => :create
     return

Why are you going with one controller action and the if/elsif/else
solution instead of just separating steps into different controller
actions?

The two controller actions are required because of 2 different security
requirements.

The before_filter :require_user only applies to the “Create” controller
action, and shouldn’t be a requirement for “Confirm” or for filling out
a
“New” entry.

From the Confirm action there is also a need to allow a “Back” step (via
posting back the params again) and this also shouldn’t require a user
constraint.

A poor-man’s solution is to manually call the “require_user” method at
the
top of the create function - but this doesn’t seem elegant and bypasses
the
whole point of the before_filter.

What I don’t understand is why doesn’t the *_filters run on every
invocation of all Controller methods (including being called from
within another method) and not just when it is called via the normal
means
of handling a routing request?

Another possible solution is of course to place 2 buttons on a page that
would point to 2 different action action handlers - but I don’t know how
one
would go about doing this without JavaScript and still be DRY.

If you have other suggestions keep it rolling :slight_smile:

  • Chris

But if you look at the sample code, I did try to call the “Create”
method as well as rendering its template
No you didnt. render :action => :create is identical to render :template
=> ‘mycontroller/create’. You might as well comment out the create
action, its not getting called at all in your current code.

Im gonna suggest that you create one controller action for each step
instead of trying to combine several steos into one action. To keep
track of the object inbetween the requests you can store it in session.

def new
@stuff = session[:stuff] || Stuff.new

session[:stuff] is used when the user clicks ‘back’ on the confirm

page
end

def confirm
session[:stuff] = @stuff = Stuff.new(params[:stuff])
end

def create
session[:stuff].save
session[:stuff] = nil
end

The way Im using sessions here isnt 100% by the book. Generally you dont
want to store entire objects in session. One of the main reasons is that
session objects can become incompatible with the code when you change
something. The best way would be to actually store the temporary object
in the database and then only store its ID in session. You can mark the
temporary object as temporary with a boolean flag or have an entire
table dedicated to temporary objects. The code above should be enough to
get you started though.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs