Confused on Advanced Rails Layout Nesting - ruby-on-rails

I currently have a few layouts that my application uses (the ... is identical between all layouts):
# application.html.erb
...
<div id="section"><div class="wrapper"><%= yield %></div></div>
...
# wide.html.erb
...
<div id="section" class="wide"><div class="container-12"><%= yield %></div></div>
...
# thin.html.erb
...
<div id="section" class="thin"><div class="container-06"><%= yield %></div></div>
...
I am looking to re-factor the code to reduce duplication, however the strange placement of the class variables (outside the yield) makes it difficult. Should I be using variables for declaring the class values within my layout (and move to a single layout) or should I be adding container.html.erb layout that contains the duplicate ... then render the three other layouts from it (or does another third option exist that I am missing)? I'm looking for the "Rails" way to do it if possible. Thanks!

module ApplicationHelper
def section_helper(outer_class=nil,inner_class)
content_tag(:div, :class=> outer_class, :id => :section) do
content_tag(:div, :class=> inner_class) do
yield
end
end
end
end
and in the layouts:
<%= section_helper("wrapper") { yield } %>
<%= section_helper("wide","container-12") { yield } %>
<%= section_helper("thin","container-06") { yield } %>
This works nicely for the first case where there is no "outer" class, since :class => nil renders nothing. But you could also pass in a hash if having an optional first argument is confusing.

We've done something like this using an instance variable like #sectionclass. Then we set it to a default in the ApplicationController and flip it to page specific values in other controllers. Then your page would be something like this:
<div id="section" class="<%= #sectionclass %>"><div class="container-12"><%= yield %></div></div>
The nice part of the instance variable is that the nil case fails silently with an empty string. (Albeit some may say this is 'bad').

Related

Using Mustache lambda functions in ERB

I'm trying to share my Mustache templates in Rails across the server and the client on the lines of this Railscast.
All is well, except that I'm unable to figure out where and how to put the definition of a lambda function on the server side.
Let's say my html.erb looks like:
<% if params['client_side'].nil? %>
<%= render 'template', :mustache => #post %>
<% else %>
<script type="text/template" id="template">
<%= render 'template' %>
</script>
<% end %>
The Mustache handler looks like this (exactly as in the Railscast):
module MustacheTemplateHandler
def self.call(template)
if template.locals.include? 'mustache'
"Mustache.render(#{template.source.inspect}, mustache).html_safe"
else
"#{template.source.inspect}.html_safe"
end
end
end
ActionView::Template.register_template_handler(:mustache, MustacheTemplateHandler)
Now for the following template:
<h1>{{title}}</h1>
<div>
{{#marked}}{{content}}{{/marked}}
</div>
the lambda marked is easy to handle in JavaScript, but how can I define it in my Rails code to render content using Redcarpet?
Update
Since posting this, I have tried to expand on the idea of helper functions in the screencast. I now have
<% if params['client_side'].nil? %>
<%= render 'template', :mustache => process(#post) %>
<% else %>
...
The process is defined in ApplicationHelper as
def process(obj)
{
marked: lambda {|text| markdown(Mustache.render(text))}
}
end
This has two problems:
text inside the (Ruby) lambda function is indeed '{{content}}', but Mustache.render(text) fails to do anything with it — it's returning an empty string.
The above code will now only render the marked field and I haven't been able to find a way to retain the other (unprocessed) attributes of the author object (e.g. title). obj.attributes seems like a promising start, but I don't know how to combine the processed response for marked with the other attributes even if #1 above worked.
I got this working myself. The process method in ApplicationHelper now looks like this (using the new lambda syntax):
def process(obj)
obj['marked'] = ->(text) { markdown(Mustache.render(text, obj)) }
obj.attributes
end
This will now catch all invocations of marked in any template.

If else statements in .html.erb in views

In rails, I often run into the situation where inside the views I'll do something like
<% if #some_condition_previusly_established_in_a_controller %>
<div class="one">123</div>
<% else %>
<div class="two">something else</div>
<% end %>
It looks a bit cluttery. Is this an acceptable way of working with views or not?
Unless you can think of a way to re-write this as a helper method, you're basically stuck with it looking kind of ugly. That's just how ERB is, as it was intended to be a minimal way of injecting Ruby into an otherwise plain-text template, not as something necessarily streamlined or elegant.
The good news is a syntax-highlighting editor will usually make your <% ... %> ERB blocks look visually different from your HTML so that can dramatically improve readability.
It's also why other representations like HAML have been created where that syntax is a lot less cluttered:
- if some_condition_previusly_established_in_a_controller
.one 123
- else
.two something else
For one or two such conditional logic in your views, I guess its fine but when your code gets bigger and you have multiple if..else..end and looks "cluttery", I think you should look at implementing "Presenter Pattern" which greatly cleans up your views by separating your logic to Presenters.
Here is a great tutorial I followed from Ryan Bates in his Rails Casts series on "Presenter Patterns from scratch". http://railscasts.com/episodes/287-presenters-from-scratch.
Have you tried?
<% #some_condition_previusly_established_in_a_controller ? <div class="one">123</div> : <div class="two">something else</div> %>
If your view contains lots of tags and HTML elements, you can put them into partials and logic into model
View:
<%= render :partial => #model.status %>
<%= render :partial => "file/path/#{#model.status}" %> # if your partial is in some different folder
If your status is one, then it would render the file _one.html.erb
If it is two, then it would render the file _two.html.erb automatically.
Model:
def status
if #some_condition
"one"
else
"two"
end
end
Yes, that is the standard (and yes, it looks cluttery).
If you're looking for a possibly cleaner alternative, check out: Conditional tag wrapping in Rails / ERB
You can always move the logic to the controller and leave the view clean(er).
Controller:
if #some_condition
#div_class = :one
#div_content = 123
else
#div_class = :two
#div_content = 'something else'
end
View:
<div class="<%= #div_class %>"><%= #div_content %></div>
Or using a helper:
<%= content_tag :div, #div_content, class: #div_class %>

Dynamic body id

How can I set ID for body tag in Rails?
<body id="login">
I don't find any of the solutions particularly elegant or flexible. Add a body_class method to application_helper.rb:
def body_class
[controller_name, action_name].join('-')
end
..in layout:
<body class="<%= body_class %>">
#e.g output: <body class="articles-show">
You can then refine the above helper to dynamically inject other identifiers such as modules.
You should use content_for tag here.
In your application layout:
<html>
...
<%= yield :body || "<body>" %>
...
</body>
</html>
And then from any view you can call this:
<% content_for :body do %>
<body id='login'>
<% end %>
That's it :)
You can set a variable #body_id in your action inside a controller or in your view and you can use it in your layout.
So for example if you have an action index you can add this code in your controller:
def index
#body_id = "myid"
end
or in your view index.html.erb as:
<% #body_id = "myid" %>
Then in your layout, I suppose application.html.erb you can add:
<body<%= " id=#{#body_id}" if #body_id %>> # no quotes around #{} are needed ;)
Be careful! The following will not work in rails 3.1
<%= yield :body || "<body>" %>
It might look like it works but if you view source you'll notice your body tag is missing unless you override it. Most browsers will work around a missing body tag.
The correct way to do is as follows
<%= content_for?(:body) ? yield(:body) : raw("<body>") %>
Be careful, you shouldn't be setting view data in your controllers, as some of the above posts have recommended. It breaks the MVC pattern, and creates needless objects that won't be used anywhere but HTML versions of your pages.
Another thing to be aware of is increased risk when you assign your body tag inside a logic block - why risk the page not outputting your body tag (breaking the page) if you don't have to? The example above:
<%= yield :body || "<body>" %>
Someone may not entirely understand your meaning in a child partial, and you're just increasing the duplication of the body tag, meaning you have more places to look if somebody doesn't terminate the tag, etc. There's no need for this risk. A more sensible solution is to yield just the class. HAML will allow you to supply optional class elements, eg:
%body{ id: yield(:body_id), class: yield(:body_class) }
And then set those values in your view:
- content_for(:body_class) { 'dashboard business' }
I'd love a solution that allows for multiple partials to add classes to the one yield block, but haven't found a non-hacky way to do this yet.
<body id="<%= #bodyid %>">
and in your controller, you can set this
#bodyid = "login"
U didnt really explain what exactly u need to do then, my first guess is:
<body id="<%= #body_id %>">

In Rails 3: Can a template image be associated with the view that's being displayed?

Sorry, I'm very new at Rails so I'll try to be as specific as I can be.
In my template I have a large "header" style image. I would like to swap that image out for another image that is associated with the view that is being displayed. Maybe this can be done using a helper? I don't even know where to begin with this.
I know I could make a bunch of template pages and load each of them with the desired view, but I think thats a lot of repeated lines of code to load when I simply want to swap one image. Does anyone have an idea?
There are a few options depending on your needs. The first thing that comes to my head is to create a couple of helper methods. One to call from your custom views and one to call from your global layout.
For example, create a file app/helpers/layout_helper.rb
module LayoutHelper
def header_image_tag
#header_image ||= 'whatever-my-default-image-is.png'
image_tag #header_image
end
def header_image(image_path)
#header_image = image_path
end
end
In your layout file... e.g app/views/application.html.erb. Something like:
<div id='banner'>
<%= header_image_tag %>
</div>
In your individual view files that you don't want the default image:
<% header_image 'other-image.png' %>
That should get you started. You may want to allow the header_image_tag to take some options to pass onto the image_tag, or set some defaults that can be overridden.
The other thing you can take advantage of is content_for and yield blocks.
Example... in your custom views, you could put something like this at the top of your view:
<% content_for :banner do %>
<%= image_tag 'blah.png' %>
<% end %>
And in your layout
<div id='banner'>
<%= yield :banner || image_tag 'my-default.png' %>
</div>

Instance variables in layout

I am fairly new to rails so I apologize if I am using the wrong terminology.
I have a model Menuitem that I would like to display the contents of in a layout. How does one go about passing an instance variable into a layout?
I was looking for a layout helper of some sort but I was unable to find anything. I was also looking at defining the instance variable in the application controller to access it in the layout, would this work? If so what is the best way to go about doing it?
Thanks!
The usual way of passing variables up from the view into the parent layout is to use the content_for method. (This answer is a copy + paste from a similar answer I posted at this question)
The normal view content gets rendered automatically into the yield call without an argument in the layout. But you can also put other placeholder content in by using yield with a symbol argument, and specifying that content from the view with content_for.
app/views/layouts/posts_layout.html.erb
<html>
<head>
<title>My awesome site</title>
</head>
<body>
<div id="someMenuStructureHere">
<%= yield(:menu_items) %> <!-- display content passed from view for menu_items -->
</div>
<%= yield %> <!-- display main view content -->
</body>
</html>
app/views/posts/index.html.erb
<%= content_for :menu_items, some_helper_to_generate_menu %>
<h1>Here is you page content</h1>
Two things I would note. First, you probably don't want to be doing this query every time you render any page in your application. You definitely want to cache your MenuItems. Second, it might be helpful to put a convenience method on MenuItems class to cache this value. So, if I define a method
def MenuItem.all_for_menu
##all_for_menu ||= MenuItem.find(:all) #returns value if exists, or initializes it
end
I can call MenuItem.all_for_menu in my layout and get all the menu items. When ever you add a new one or edit one, you'd have to invalidate that.
Another caching approach would be to put the data in a partial and cache that fragment using the standard caching call:
<% cache(:controller => "menu_items",
:action => "list",
:action_suffix => "all_menu_items") do %>
<%= render :partial => "menu", :collection => MenuItem.all_for_menu %>
<% end %>
You can then expire that fragment by calling:
expire_fragment(:controller => "menu_items", :action => "list", :action_suffix => "all_menu_items")
Any instance variables defined in the controllers are auto-magically available in your views. If you are expecting an instance variable in your layout for all actions, you may want to consider defining the instance variable in a before_filter or encapsulating it in a controller method and using helper_method to make it accessible in your views.
It really depends on what you want to do with the model. I'll just guess, and you tell me what you need different to understand better how to do this. This code would work only if your MenuItem model has a field named name.
In the controller:
# Use whatever action you are currently displaying
def index
#menu_items = MenuItem.all
end
In the index.html.erb view file:
<ul id="menu">
<% #menu_items.each do |menu_item| %>
<%= h menu_item.name %>
<% end %>
</ul>
Obviously if this was a real menu, there would be hyperlinks there too :)
items_controller.rb (or something)
def show
#menu_item = MenuItem.find(params[:id])
end
In the view show.html.erb:
<%= #menu_item.name %>

Resources