Rails conditional ('if') statements based on controller action - ruby-on-rails

There might be a better way to do this, but I'm trying to make an if statement in rails, based on the current action, in a controller (this will be used in a view).
For example, if its the edit page, or the show page, etc. I'd like a different style for something - is there an if statement that can specify this?
(I need an if statement, because its used in a partial, on multiple pages).
Thanks!
Elliot

The params hash that is available in the controller contains :controller and :action keys, which specify the controller and action names of the request.
Therefore you could say
if params[:action] == "foo"
# Show stuff for action 'foo'
elsif params[:action] == "bar"
# Show stuff for action 'bar'
elsif ...
# etc.
end

It's not good practice IMO to have partials asking what the current controller and action names are. Think "tell, don't ask" (http://www.pragprog.com/articles/tell-dont-ask). That is, rather than having the partial ask it's caller about its state, tell the partial what you want it to do.
One way to do this is by passing variables to the partial through the locals option:
<%= render :partial => "/common/toolbar", :locals => {:edit => true} %>
Then in the partial:
<% if defined?(edit) && edit %>
... stuff appropriate to edit mode
<% end %>

You can do it this way:
class ApplicationController < ActionController::Base
layout :set_layout
def set_layout
case params[:action]
when "foo"
"foo_layout"
when "bar"
"bar_layout"
...
else
"default_layout"
end
end
...
end
hope it helps =)

You can use layouts for partials too:
<%= render :partial => 'some_partial', :layout => 'wrap_with_stuff' %>
If you want to work out what layout to use dynamically I'd put that in a helper. So you'd end up with
# In your view
<%= render :partial => 'some_partial', :layout => layout_for_my_partial %>
# In your helper
def layout_for_my_partial
params[:action] == 'show' ? 'show_wrapper' : 'everything_else_wrapper'
end
This will only work in some circumstances, but might be what you're trying to do.
See more here.
http://ryandaigle.com/articles/2007/8/3/what-s-new-in-edge-rails-partials-get-layouts

Related

Conditional Rendering of partial in Rails

I have the controller:
class PagesController < ApplicationController
protect_from_forgery
layout "pages"
def index
end
def products
end
def company
#enable_sub_menu = true
end
def support
end
def login
end
end
routes file:
App::Application.routes.draw do
root :to => 'pages#index'
##Product / Search Routing
match "products" => "pages#products"
match "products/search" => 'pages#products/search'
match "products/search/pricing" => 'pages#products/search/pricing'
match "products/business/pricing" => 'pages#products/business/pricing'
match "products/business" => 'pages#products/business'
##Company Pages Routing
match "company/team" => 'pages#company/team'
match "company/contact" => 'pages#company/contact'
match "company" => 'pages#company'
match "company/friends" => 'pages#company/friends'
##Support Routes
match "support" => 'pages#suppprt'
##Login Routes
match "login" => 'pages#login'
end
What I am trying to do is on any page the is /company I want to render a partial but on no others to do this I am using this
<%= render :partial => "pages/partials/sub_nav" if #enable_sub_menu %>
Which looks in the controller method and checks to see if it should load the sub_nav partial
It works great for /company but it does not work for sub pages of /company such as /company/team
How can I enable it to load for all sub pages of the method company in the controller?
There is a helper method in your views called controller_name ActionController::Metal
This would probably allow you to trigger the partial based upon the controller you're in.
<%= render :partial => "pages/partials/sub_nav" if controller_name == "company" %>
Note there is also a helper called action_name that allows you to check against the current action too. So you could combine them.
<%= render :partial => "pages/partials/sub_nav" if controller_name == "company" || action_name == "company %>
Of course you'll probably want to roll this if statement up into a helper method in your ApplicationHelper to DRY up your view

HAML partials - partial does not recognized object which I queried for in the controller

I have this controller:
def index
#disclosures = Disclosure.where(:user_id => current_user.id)
respond_to do |format|
format.html{}
format.js{}
end
end
and with the help of the good folks at StackOverflow I am now able to get my HAML to point to the partial like this:
= render :partial => "/influencers/disclosures/shared/list"
but this partial throws and exception:
-if disclosures.empty?
.alert.alert-info
%p=(no_disclosures_message || (t "influencers.influencer_dashboard.disclosures.no_disclosures"))
%table.table.influencer-disclosures
%tbody
-disclosures.each do |disclosure|
=render "influencers/disclosures/shared/row", :disclosure => disclosure
saying that:
undefined local variable or method `disclosures' for #<#<Class:0x133ca8a58>:0x133ca25e0>
But how can this be? I just queried for that disclosures object in my controller. Any idea why this is happening and how to fix it?
Thanks!!
You need to put an # in front of disclosures. This is how the controller passes variables to the view.
-if #disclosures.empty?
and
-#disclosures.each do |disclosure|
Update
Another way to fix this is the change your render call. This will make it backwards compatible with other call sites of the same partial.
render :partial => "/influencers/disclosures/shared/list", :locals => {:disclosures => #disclosures}

Where does the site-wide footer logic belong in a Rails 3 app?

I have a site-wide footer that should display a list of recent Users and Posts. I'm wondering where the logic should to gets this data. Should I have a "recent_users" method in the UsersController and a "recent_posts" method in the PostsController, or should I have a separate FooterController?
How about a _recent_users partial views/users and a _recent_posts partial in views/posts and have the footer partial render both of them?
All "business logic" should be put in the Model, not the controller. The query for recent Users and Posts should be in the User and Post model. Then, if you have a site-wide view element, move it into a partial and add that partial into the application.html.erb.
# User.rb
model User
def recent
# logic and query here
end
end
# Post.rb
(see above)
# application_controller.rb
before_filter :get_recent_posts
before_filter :get_recent_users
...
private
def get_recent_posts
#recent_posts = Post.recent
end
def get_recent_users
#recent_users = User.recent
end
# application.html.erb
...
<%= yield %>
...
<%= render :partial => 'layouts/footer', :locals => { :recent_users => #recent_users, :recent_posts => #recent_posts } %>
# layouts/_footer.html.erb
<% recent_users.each do |user| %>
<%= link_to user.name, user %>
<% end %>
# same for posts
A few important things to note:
don't access the instance variables (the #foo) in the partial... pass it into the locals hash and access it as a variable instead. It's just generally bad practice
you could also use a module
look into caching because you probably don't want to hit your database TWICE on every page load. You could use fragment caching on the footer and expire it every 15 minutes (probably the best option).

Rails3 rendering action from another controller

i want to render an action from another controller, but i get the error:
undefined method `formats' for nil:NilClass
<script>
$("#validate_company").live("keyup", function() {
$("#company_info").html("<%= escape_javascript(render(:controller => 'live_validation', :action => 'validate_client_company')) %>");
});
</script>
This is the Controller:
class LiveValidationsController < ApplicationController
def validate_client_company
if params[:first_name].length > 0
#client = Client.find_by_company(params[:company])
if #client.nil?
#message = "<img src='/images/accepted_48.png' alt='Valid Username'/>"
else
#message = "<img src='/images/cancel_48.png' alt='Invalid Username' /> Name taken"
end
else
#message = ""
end
render :partial => "message"
end
end
The partial _message is just
<%= #message %>
You seem to be mixing up stuff.
You have html inside your controller-method? That should be in your view. For each controller-method, normally a view with the same name is rendered, except if you explicitly call render from within your controller-method.
You do not write html in your controller. You write html in the view, and sometimes you have helpers to make your views more readable.
Secondly, in your first piece of code, which is some view-code, i hope. The view is prepared at server-side and then sent to the client. You can render another view, a partial, from a view. But this does not load data live.
How i would fix this. Inside your views where you want to dynamically render the validation:
<script>
$("#validate_company").live("keyup", function() {
$("#company_info").load("<%= url_for :controller => 'live_validations', :action => 'validate_client_company' %>");
});
</script>
Then inside your controller you write:
class LiveValidationsController < ApplicationController
def validate_client_company
if params[:first_name].length > 0
#client = Client.find_by_company(params[:company])
#error = #client.nil? ? :valid_username : :invalid_username
else
#error = nil
end
render :partial => "message", :layout => false
end
end
Inside your app/helper/live_validations_helper.rb you add a method
def get_validation_message(error)
if error == :invalid_username
image_tag('/images/cancel_48.png', :alt => 'Invalid Username') + "Name taken"
elsif error == :valid_username
image_tag('/images/accepted_48.png', :alt => 'Valid Username')
end
end
and inside your message view you write something like:
<%= get_validation_message(#error) %>
render :action => does not run the associated controller method.
It simply renders the template that Rails would, by default, have associated with the action. That is to say, if the action validate_client_company simply called render without passing any arguments, Rails would look for a template in a folder with the same name as the controller and with a name with the same name as the action. Calling render :action => simply looks for that same template and renders it.
Best guess is that Rails cannot find a template named validate_client_company. I expect that none exists, because the action validate_client_company renders a partial named message instead of rendering a template with the default name for that action.
In the action which renders the script, you need to set up the instance variables and then in the template for that action you need to use the following:
render :partial => 'live_validations/message'
It certainly makes sense to have mini MVC stacks within the larger MVC stack, so that you can run sub-actions within running larger actions. For this scenario, you may wish to look at Cells. However, you cannot do this with Rails by itself.
You probably want something like this:
<script>
$("#validate_company").live("keyup", function() {
$("#company_info").load("<%= url_for :controller => 'live_validation', :action => 'validate_client_company' %>");
});
</script>
But the rest of your code is kinda of messed up... Rendering a partial from inside a controller?
Got it to work!
<script>
$("#validate_company").live("keyup", function() {
$("#company_info").load("/live_validations/validate_client_company",{company:$('#validate_company').val()});
});
</script>

Rendering a collection of different classes in Rails

I have a collection that contains instances of several different classes, and I want to render the partial for each instance. I can do this using the following code:
<%= render #results %>
My question is: How can I render the different partials in a different base directory? The above code will look for app/views/stories/_story.html.erb, however, the partials for this action are all kept in a different directory - app/search/_story.html.erb. Is there any way of specifying this?
You could create a helper method like this:
def render_results(results)
result_templates = {"ClassA" => "search/story", "ClassB" => "something/else"}
results.each do |result|
if template = result_templates[result.class.name]
concat render(:partial => template, :object => result)
end
end
end
And then in the view call <% render_results(#results) %>
or you can use is_a?(Object)
if is_a?(classA)
render something_A
elsif is_a?(classB)
render something_B
end
I have a similar situation where I have multiple classes so I use a partial for each class like:
for result in #results
= render :partial => "result_#{result.class.to_s.downcase}", :locals => {:item => result}
end

Resources