I am trying to apply a specific layout to one action in a controller in my Rails (v. 4.2.5) app, but it will not work. Oddly (or maybe not so oddly), the layout will be used used to render an action if that action is part of the 'resources' route, but not to the action that I need this to work for, which is not part of 'resources'. Sorry if that seems confusing, here's the relevant code and explanation...
routes.rb - here I have the standard resources routes for my entries controller, as well as a an additional route for 'inputs'
get '/entries/inputs' => 'entries#inputs.html'
resources :entries
entries_controller.rb - here I'm trying to apply the layouts/cached.html.erb to the 'inputs' action
class EntriesController < ApplicationController
layout "cached", only: [:inputs]
def inputs
end
def index
#entry = Entry.all
end
end
As it is, layouts/cached.html.erb does not get applied to the 'inputs' action. However, if I swap out the second line of code in the controller for this:
layout "cached", only: [:index]
the layout is successfully rendered for the 'index' action.
What am I missing here? Why will this layout apply to one action but not the other?
Use this code:
class EntriesController < ApplicationController
layout :resolve_layout
def inputs
end
def index
#entry = Entry.all
end
private
def resolve_layout
case action_name
when "inputs"
"cached"
else
"application"
end
end
end
And in Routes:
get 'entries/inputs' => 'entries#inputs'
Related
How do you setup your views, controllers and routes?
One controller for everything the control panel does, or many?
Firstly, let's try to think how we would view the various panels. Let's say our control panel is pretty simple. We have one panel to show all the users who have signed-up and can CRUD them, and another panel to show all of the images that have uploaded, and we can carry up CRUD on those too.
Routes:
scope path: 'control_panel' do
get 'users', to: 'panels#users', as: :panel_show_users
get 'photos', to: 'panels#photos', as: :panel_show_photos
end
Controller:
class PanelsController < ApplicationController
def users
#users = User.all
end
def photos
#photos = Photo.all
end
end
View file structure:
panels
|_ users.html.erb
|_ photos.html.erb
Okay, now I don't see any problems with that, to simply access the panels and populate the views with data. Do you see any problems?
Here is where I'm sort of at a cross roads though. What should I do when I want to Created Update and Delete a user/photo? Should I put them all in the PanelsController?
class PanelsController < ApplicationController
before_action :protect
def users
#users = User.all
end
def update_user
#user = User.find(params[:id])
#user.update(user_params)
end
def photos
#photos = Photo.all
end
def update_photo
#photo = Photo.find(params[:id])
#photo.update(photo_params)
end
private
def protect
redirect_to root_url, error: 'You lack privileges!'
end
end
While this would result in a large PanelsController, it would feel good to be able to execute that protect action and just one controller hook. It would also mean the routes would be easy to setup:
scope path: 'control_panel' do
get 'users', to: 'panels#users', as: :panel_show_users
post 'user', to: 'panels#update', as: :panel_create_user
get 'photos', to: 'panels#photos', as: :panel_show_photos
post 'photos', to: 'panels#photos', as: :panel_create_photo
end
I should use resource routes here?
Like I say, this will result in a huge panels controller, so I was thinking it may be better to have a separate controller for each resource and then redirect to panels views?
Routes:
scope path: 'control_panel' do
resources :users
resources :photos
end
Controllers:
class UsersController < ApplicationController
def index
end
def show
end
def new
end
def create
end
def update
end
def destroy
end
end
class PhotosController < ApplicationController
def index
end
def show
end
def new
end
def create
end
def update
end
def destroy
end
end
Still some quirks though. I have my Users#index action there, but what if I have two routes that return an index of all users? In the control panel, but also, when people are searching for another user, for example. Should I have two routes in the User controller? def public_users and def control_panel_users? That may be the answer. Could setup a hook to run #users = User.all in both of them, but redirect to a different location, and not have the protect method redirect them.
How should I protect these routes from non-admins? Should I move my protect method into the the application controller? Wouldn't this be a bit fiddly to setup?
class ApplicationController < ActionController
before_action :protect
def protect end
end
class StaticController < ApplicationController
skip_before_action [:home, :about, :contact]
def home
end
def about
end
def contact
end
end
But that is my question. 1 control panel controller or no control panel controller.
I really wish there was more advanced tutorials out there :( Billions of books on CRUD, MVC and things, but nothing on advanced things like control panels and AJAX...
Don't have a control panel controller. And to protect stuff from non-admins, use namespacing - read more about it here: http://guides.rubyonrails.org/routing.html#controller-namespaces-and-routing
You can protect your 'admin'-namespaced controllers with authentication, and have the non-namespaced controllers open to the public (or open to non-admin users)
With regards to your def public_users and def control_panel_users question, you could just have two def index methods - one in the non-namespaced controller, and one in the admin-namespaced controller. They would each do different things.
So, you'd have 4 controllers in total:
2 non-namespaced, one for users, one for photos (containing all public stuff)
2 admin-namespaced, one for users, one for photos (containing all control panel stuff)
If you wanted, rather than using 'admin' as the namespace, you could use some other term you prefer - like 'panel'. 'Admin' is pretty conventional though.
I'm a Rails beginner and I learn that I always must try to be more DRY.
I'm have a comment system associated to my content model, and I load my comment with ajax on page scroll.
In my view I have:
%section.article-comments{'data-url' => content_comments_path(#content)}
and in my routes.rb file I have the route
resources :contents, only: :index do
resources :comments, only: :index
end
My comment controller of course is
def index
#content = Content.find(params[:content_id])
#comments = #content.comments
render ...
end
Now I want to add comments also to videos and gallery.
So I need to add a route for every resource and I need a gallery_index and a video_index.
Content, video and gallery index method in comment controlelr are repeated, and I cannot understand how can I be more DRY.
All your controllers presumably inherit from ApplicationController:
class CommentsController < ApplicationController
If you find yourself with a lot of repetition in any of the controller methods you could define it in ApplicationController instead, with maybe some specific processing in each controller.
For example:
class ApplicationController < ActionController::Base
def index
...some common processing...
specific_index_processing
end
private
def specific_index_processing
# empty method; will be overridden by each controller as required
end
end
class CommentsController < ApplicationController
private
def specific_index_processing
...specific procesing for the comments index method...
end
end
And of course, if one of your controllers needs to be completely different from this common approach you can always just override the entire index method.
I wonder what is a proper way to resolve the following problem.
I have two models :
Publisher and Issue. Publisher has_many issues.
I want to manage issues both from Publishers list and from Issues list.
For example :
on publishers list user can click link "Issues" placed near each publisher. then he goes to Issues list but filtered only for proper publisher. He can click "create new issue" and goes to form for add new issue. On this form i don't need to show him a select list to choose publisher
on Issues list user can click "create new issue" and go to form but this time he should choose publisher from select, which will be related to created issue.
Simply speaking i need crud action for issue alone and for publisher issue.
First I try to make a :
resources :issues
resources :publishers do
resources :issues
end
and in issue controller :
before_filter :find_issue
def find_issue
#publisher = Publisher.find(params[:publisher_id]) if params[:publisher_id]
#issues = #publisher ? #publisher.issues : Issue
end
but i have to make many if condition in my views and controller.
For example if issue is created from publisher , on success i want to redirect to publisher_issues_path instead of issue_path a vice versa. Same problem with all link like "back to list" and similar. So code is in my opinion not very transparent.
Now i wonder to use namespaces.
namespace :publishers, do
resources :issues
end
and make
# app/controllers/publishers/issues_controller.rb
module Publishers
class IssuesController < ApplicationController
# actions that expect a :publisher_id param
end
end
# app/controllers/issues_controller.rb
class IssuesController < ApplicationController
# your typical actions without any publisher handling
end
and make separate view for both controller actions.
Is there a better or cleaner way to resolve this kind of problem? I want to make my code dry as possible.
Many thanks for reply.
Routes:
resources :issues
resources :publishes do
resources :issues
end
Controller:
class IssuesController < ApplicationController
before_filter :build_issue, only: [:new, :create]
before_filter :load_issue, only: [:show, :edit, :update, :destroy]
def index
#issues = issues.page(params[:page])
end
... all other actions will have access to #issue
private
def issues
if params[:publisher_id].present?
Publisher.find(params[:publisher_id]).issues
else
Issue
end
rescue ActiveRecord::RecordNotFound
redirect_to issues_path(alert: 'Publisher not found')
end
def build_issue
#issue = issues.new(issue_params)
end
def load_issue
#issue = issues.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to issues_path(alert: 'Issue not found')
end
def issue_params
# whitelisted attributes goes here
end
end
To avoid using conditions, use actions instead of full named path, i.e:
redirect_to action: :index
link_to 'Issues', {action: :index}
If I would like to use a layout for a certain action (say, the show action) that is different from the layout declared at the top of the controller.rb file, how could I do this? This must be possible in rails, but I cannot seem to find anything about it.
render :layout => 'otherlayout'
layout 'layout', :only => [:first_action, :second_action]
layout 'second_layout', :only => [:third_action, :fourth_action]
Don's is right as well, just depends on your application which is more DRY (or DRY-er?)
EDIT
My previous code is mistaken. You cannot specify the layout function multiple times. I found this solution online for dynamic layout rendering:
class OrdersController < BaseController
layout :determine_layout
private
def determine_layout
%w(new).include?(action_name) ? "some_layout" : "public"
end
end
Source: apidock.com/rails/Actio...
Following example applies desired layout to specific action, otherwise, it uses default layout (layouts/application.html.erb).
class ArticlesController < ApplicationController
layout "article_editor", only: [:new, :edit]
def index
# default layout
end
def new
# article_editor layout
end
def edit
# article_editor layout
end
end
I need message to have a different layout in project, is it possible in rails to do something like this?
Class Messages::New < #project? ProjectLayout : NormalLayout
end #i treid this, don't work, since #project has not been initiated.
thanks
this may help you
class MessagesController < ApplicationController
layout :get_layout
def get_layout
#project? ? 'ProjectLayout' : 'NormalLayout'
end
end
Also, since the question is unclear, you can also set layout for only one action with the render option.
render :action => 'new', :layout => 'layoutname'
You can only apply layouts at the Controller level:
class MessagesController < ApplicationController
layout :project
end
Layout method documentation has an example on how to do conditional layouts
You can only apply the rails layouts at controller level and individual action levels.
Unique layout in for each controller
class MessagesController < ApplicationController
layout "admin"
def index
# logic
end
end
** The above line layout "admin" will load the admin layout each time the message controller gets invoked. For that, you must have a layout created in your layouts/admin.html.rb file.**
Dynamic layout for each controller
class MessagesController < ApplicationController
layout :dynamic_layout
def index
# logic
end
protected
def dynamic_layout
if current_user.admin?
"admin" # Show admin layout
else
"other_layout" # Show other_layout
end
end
end
# Individual Action Level Layouts
If you want to display different layouts for each action you can do that.
class MessagesController < ApplicationController
layout :dynamic_layout
def index
# logic
render :action => 'index', :layout => 'index_layout'
end
def show
# logic
render :action => 'show', :layout => 'show_layout'
end
end
Decide the layout in the controller rather then the model. Your ProjectsController can use it's own ProjectLayout and MessagesController can then use the normal layout if you wish.
My two cents in ApplicationController:
before_action :layout_by_action
##actions = %w(new edit create update index)
def layout_by_action
if ##actions.include? params[:action]
self.class.layout 'admin'
else
self.class.layout 'application'
end
end
You can use a Proc:
layout -> {
if something?
'my-layout'
else
'my-other-layout'
end
}