Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #8867 (new defect)

Opened 1 year ago

Last modified 7 months ago

[PATCH] Multiple "layout" declarations gives unexpected results

Reported by: Henrik N Assigned to: core
Priority: normal Milestone: 2.x
Component: ActionPack Version: edge
Severity: normal Keywords: actioncontroller layouts
Cc: sur

Description

Multiple "layout" declarations don't work as one might expect:

class UsersController < ApplicationController
  layout "one", :only => [:this, :that]
  layout "two", :except => [:this, :that]
end

should intuitively make the "one" layout apply to the "this" and "that" actions and the "two" layout to all others. This would be in line with how before_filter etc work.

It does not work, however. Having multiple "layout" declarations messes things up (in various ways depending on options, it seems).

I think preferably, multiple layout declarations should work alongside each other. In case they overlap, e.g.

layout "one", :only => [:this, :that]
layout "two", :only => [:that, :the_other]

then the last declaration should have precedence: so in this case, the "one" layout should apply to "this"; the "two" layout should apply to "that" and "the_other".

Not entirely sure if this should be a "defect" or "enhancement". Going with "defect" as I find it inconsistent and counter-intuitive.

No patch, alas. Might look into it if I find the time, but I thought I should at least report it in case someone more familiar with the code base wants to have a go.

I'm aware you can use procs/methods with "layout" to achieve the expected effect in the example above, but being able to use multiple declarations would be more intuitive and read better.

If core disagrees with this suggested behaviour, an alternative could be to throw a DoubleLayoutDeclarationError, analogous to DoubleRenderError, so people don't end up with multiple declarations silently producing weird results.

Attachments

clear_layout_conditions.diff (0.7 kB) - added by mikong on 07/14/07 18:35:34.
test_multiple_layout_with_only_and_except_conditions.diff (0.8 kB) - added by mikong on 07/15/07 12:30:13.
layout.zip (68.1 kB) - added by vocaro on 07/16/07 19:16:21.
Test case to illustrate the bug described in the original report

Change History

07/06/07 01:33:41 changed by marclove

Seems like this should be two separate tickets to me. One for your reported defect of

layout "one", :only => [:this, :that]
layout "two", :except => [:this, :that]

not working and another for your suggested enhancement of a DoubleLayoutDeclarationError.

Also, could you give a more specific about what errors and in what situations you're encountering errors with the first problem?

07/14/07 17:30:08 changed by mikong

Please first check the discussion of Inheritance in the documentation of layout. Note that a parent controller calling the layout method, and then the child controller overriding the parent's layout by also calling the layout method, is the same as calling the layout method twice like in the case above. So we cannot implement the DoubleLayoutDeclarationError. We also cannot combine the logic of multiple layout calls because the design is that later calls override previous calls. I think the only problem that we should fix is the current messed up behavior.

I've just debugged the behavior for multiple layout method calls. Let us take this case given:

layout "one", :only => ["this", "that"]
layout "two", :except => ["this", "that"]

In the inheritable attributes hash, it will contain

"layout" => "two"

and

"layout_conditions" => { :only => ["this", "that"], :except => ["this", "that"] }

This will cause the strange behavior mentioned. In my tests of this case of :only and :except, the :only condition is followed instead of the :except condition. Hence, "this" and "that" will use the "two" layout.

So even though the layout is overridden as expected, the problem is that the layout conditions are merged. The fix to this problem, IMHO, is to clear the layout conditions at the start of the layout method.

I'll attach the patch very soon.

(follow-up: ↓ 6 ) 07/14/07 17:35:53 changed by mikong

Anyway, the intuitive multiple layout declarations in a single controller seems like a good idea but may be better implemented as a plugin because it currently conflicts with the design of the layout mechanism.

07/14/07 18:11:16 changed by iamteem

Hi.

I think the last template specified will always be used in rendering the layout. For the case given, the 'two' template will always be used.

A work-around for the merging of :except and :only conditions is to set them all the time (nil if you want to clear parent's conditions). This allows the inheritance of :only and :except conditions from the parent class, although always doing this could be a pain and confusing.

07/14/07 18:35:34 changed by mikong

  • attachment clear_layout_conditions.diff added.

07/15/07 12:30:13 changed by mikong

  • attachment test_multiple_layout_with_only_and_except_conditions.diff added.

07/15/07 12:31:28 changed by mikong

  • summary changed from Multiple "layout" declarations gives unexpected results to [PATCH] Multiple "layout" declarations gives unexpected results.

07/16/07 19:16:21 changed by vocaro

  • attachment layout.zip added.

Test case to illustrate the bug described in the original report

(in reply to: ↑ 3 ; follow-up: ↓ 8 ) 07/16/07 23:36:17 changed by vocaro

Replying to mikong:

Anyway, the intuitive multiple layout declarations in a single controller seems like a good idea but may be better implemented as a plugin because it currently conflicts with the design of the layout mechanism.

Okay, let me make sure I understand this... In the current version of Rails (1.2.3), a controller can be associated with no more than one layout. Although some actions may have no layout whatsoever (e.g., layout "foobar", :except => :some_action), it's not possible for two actions in the same controller to be associated with two different layouts. Is that correct?

(follow-up: ↓ 9 ) 07/17/07 00:04:25 changed by timocratic

When considering this one, especially with the "it should be a plugin" mindset, please keep in mind that even really simple inherited layout declarations get all fubarred due to it, as in #3647

(in reply to: ↑ 6 ; follow-up: ↓ 10 ) 07/17/07 00:33:11 changed by mikong

Replying to vocaro:

Although some actions may have no layout whatsoever (e.g., layout "foobar", :except => :some_action), it's not possible for two actions in the same controller to be associated with two different layouts. Is that correct?

You can set the layout in the action's method itself using render. Or maybe use the layout with a method reference.

(in reply to: ↑ 7 ) 07/17/07 01:17:36 changed by mikong

Replying to timocratic:

When considering this one, especially with the "it should be a plugin" mindset, please keep in mind that even really simple inherited layout declarations get all fubarred due to it, as in #3647

I was only referring to the multiple layout declarations in the same controller. This is because currently, the layout template name is just stored as an inheritable attribute of the controller and declaring multiple layouts in the same controller just override the attribute, Handling this means changing the way layout is stored. Inherited layout declarations on the other hand, retains the information of the layout templates per controller and so this can be handled without moving the layout information somewhere else. So I'm not saying this should be a plugin. In a related ticket, #8737, I said handling inherited layouts may be better as a plugin but I took my words back because I realized layout info is retained.

Anyway, if a not-too-complicated patch can handle multiple layout declarations (whether in the same controller or through inheritance), then it is good to have it in the core instead of just a plugin. I only suggested it to be a plugin so that it can be tested thoroughly.

(in reply to: ↑ 8 ) 07/17/07 01:37:23 changed by vocaro

Replying to mikong:

You can set the layout in the action's method itself using render. Or maybe use the layout with a method reference.

Good point; I had forgotten about that. You can simply remove the 'layout "two"' declaration and then add the following to the end of the action:

render :layout => "two"

Not as elegant as if the layout command were working as expected, but still an adequate fix.

You just have to be careful that you don't have any redirect_to commands elsewhere in the action, otherwise you'll get ActionController::DoubleRenderError.

07/17/07 03:05:22 changed by timocratic

mikong - I wasn't meaning to sound snippy. I just wanted to be sure that people are considering the complications of the inherited cases on :except and :only. The current effects are very non-obvious for that.

08/31/07 01:05:04 changed by sur

  • cc set to sur.
  • keywords changed from actioncontroller to actioncontroller layouts.

03/03/08 02:06:56 changed by dasil003

Currently it appears that:

render :layout => 'foo', :only => [:action_one, :action_two]

will result in no layout at all being used for :action_three. That seems incredibly broken to me, and maybe it's just me. But I think it's definitely worth a major patch to allow for multiple layout calls to be honoured.

03/03/08 04:54:57 changed by sur

though we can use this until the patch doesn't get committed...

class UsersController < ApplicationController::Base
  layout :set_layout

  private
  def set_layout
    case action_name
    when 'action_one', 'action_two' then 'foo'
    when 'action_three' then 'bar'
    else 'users'
    end
  end
end