Devise customisation of views - ruby-on-rails

Is there a way to route devise users to a login and signup screen that doesn't output the header and footer? as everything is being yielded to my application.html.erb
Heres my current code for application.html.erb
<body>
<div id="wrap">
<%= render 'layouts/header' %>
<%= yield %>
<div id="push"></div>
</div>
<%= render 'layouts/footer' %>
</body>

Controllers support :only and :except options for layouts so you can restrict access in the controller like this:
class RandomController < ApplicationController
layout 'application', :except => [:signup, :signin]
def signin
// some code
end
def signup
// some code
end
end
I'd recommend you view the official RoR website section (this link) on rendering views.
Update 2
Set the layout for specific Devise controllers using a callback in config/application.rb.
(so this code belongs in the /config/application.rb file)
This allows for layouts to be specified on a per-controller basis. If, for example, you want a specific layout assigned to Devise::HomeController views:
config.to_prepare do
Devise::HomeController.layout "layout_for_home_controller"
end
A more indepth example using four different layouts, one for each controller:
config.to_prepare do
Devise::HomeController.layout "layout1"
Devise::UsersController.layout "layout2"
Devise::ArticlesController.layout "layout3"
Devise::TutorialsController.layout "layout4"
end

Not the prettiest solution, but this is a way of doing it.
<body>
<div id="wrap">
<%= render 'layouts/header' unless params[:controller] == "devise/sessions" or "devise/registrations" and params[:action] == "new" %>
<%= yield %>
<div id="push"></div>
</div>
<%= render 'layouts/footer' unless params[:controller] == "devise/sessions" or "devise/registrations" and params[:action] == "new" %>
</body>
EDIT:
application_helper.rb
def hidden_header_footer
params[:controller] == "devise/sessions" or "devise/registrations" and params[:action] == "new"
end
application.html.erb
<%= render 'layouts/footer' unless hidden_header_footer %>
<%= render 'layouts/header' unless hidden_header_footer %>

If you're using Devise with the standard layouts, they're render into Main view: <%= yield %>.
So you can change your application.html.erb in:
<body>
<div id="wrap">
<% if user_signed_in? %>
<%= render 'layouts/header' %>
<% end %>
<%= yield %>
<% if user_signed_in? %>
<%= render 'layouts/footer' %>
<% end %>
</div>
</body>
And your application_controller.rb in:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
#before_action :authenticate_user!, except: [:index, :home]
before_action :authenticate_user!
end

Related

Rails 7: Turbo calls not existing create method

I have a problem. In my Rails app, I am trying to load a list of data using given select-option elements. When I hit submit, I am getting an error that the create method doesn't exist in my controller (which is true). I am new with this turbo-rails package, but this is what I got so far:
index.html.erb
<!DOCTYPE html>
<html>
<head>
<%= stylesheet_link_tag 'users/main', 'data-turbolinks-track': 'reload' %>
<%= csrf_meta_tags %>
</head>
<body>
<main>
<div class="grid">
<div class="users">
<div class="filters">
<form action="/users" method="POST">
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
<%= render partial: "shared/select", locals: {
placeholder: 'Gender',
width: '90px',
options: $genders,
classes: 'filter',
name: 'gender'
} %>
<button type="submit">submit</button>
</form>
</div>
<div id="user-data-list">
<%= render partial: "users/user_list", locals: {
users: #users
} %>
</div>
</div>
</div>
</main>
</body>
</html>
users.controller.rb
class UsersController < ApplicationController
before_action :require_user
USERS_PER_PAGE = 15
def index
#users = User.limit(USERS_PER_PAGE).order(:creation_date).reverse_order
end
def load
gender = if params[:gender] then params[:gender] else '' end
#users = User.limit(USERS_PER_PAGE).where(
"gender LIKE ?", "%" + gender + "%"
).order(:creation_date).reverse_order
respond_to do |format|
format.turbo_stream
format.html { redirect_to users_url() }
end
end
end
load.turbo_stream.erb
<%= turbo_stream.replace "user-data-list" do %>
<%= render partial: "users/user_list", locals: {
users: #users
} %>
<% end %>
routes.rb
Rails.application.routes.draw do
# System check
get '/health', to: 'application#health'
get '/users' => "users#index"
post '/users' => "users#load"
resources :users
post '/login' => 'auth#login'
get '/logout' => 'auth#logout'
end
_select.html.erb
<!DOCTYPE html>
<html>
<head>
<%= stylesheet_link_tag 'components/select', media: 'all', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<select
style="<%= 'width: ' + width + ';' if width %>"
<% if defined?(id) %>
id="<%= id %>"
<% end %>
<% if defined?(classes) %>
class="<%= classes %>"
<% end %>
<% if defined?(name) %>
name="<%= name %>"
<% end %>
<% if defined?(required) %>
required
<% end %>
>
<option
value=""
disabled
selected
hidden
>
<%= placeholder %>
</option>
<% options.each do |option| %>
<option><%= option %></option>
<% end %>
</select>
</body>
</html>
But when I run this code and hit the submit button I get the following error:
I don't have a create method in my controller, because there is no possible option to create a user. I have a custom load method, and I can't seem to figure out why it is trying to call the create method. Can someone explain this to me?
Routes have priority in the order they are declared.
resources :users already declares a POST /users route. While you could fix it by moving your strange route up you don't actually even need this in the first place.
What you're doing looks like you're simply adding a bunch of filters to a resource. You can do that by just sending a GET request to the collection path (the index) and having it use the optional query string parameters to apply conditions.
Rails.application.routes.draw do
# System check
get '/health', to: 'application#health'
# Only declare the routes you're actually using!
resources :users, only: [:index]
post '/login' => 'auth#login'
get '/logout' => 'auth#logout'
end
<%= form_with(url: users_path, method: :get, data: { turbo_frame: "user-data-list" }) do |form| %>
<%= render partial: "shared/select", locals: {
placeholder: 'Gender',
width: '90px',
# why is this a global?
options: $genders,
classes: 'filter',
name: 'gender',
form: form # pass the form builder along
} %>
<%= form.submit %>
<% end %>
# /app/users/index.turbo_stream.erb
<%= turbo_stream.replace "user-data-list" do %>
<%= render partial: "users/user_list", locals: {
users: #users
} %>
<% end %>
class UsersController < ApplicationController
before_action :require_user
USERS_PER_PAGE = 15
def index
#users = User.limit(USERS_PER_PAGE)
.order(:creation_date: :desc)
if params[:gender]
# and why is this pattern matching?
#users.where("gender LIKE ?", "%#{gender}%")
end
respond_to do |format|
format.turbo_stream
format.html
end
end
end
Conceptually performing a search, adding pagination or any other kinds of filters via query strings parameters are idempotent actions as its not creating or altering anything and the resource will look the same for any visitor using the same URI (in theory at least).
You should thus use GET and not POST which lets the request be saved in the browsers history - be cached, and be directly linkable.

I want to eliminate header or footer from some of the pages in my rails rails app

I am working in rails project. I created a header and footer and added to all pages in layouts/application.html.erb file. Now I want to remove it from a particular page.
Each page of a rails app is an action nested inside a controller.
You can see the current controller_name and action_name by adding the below to a view:
<%= controller_name %>
<%= action_name %>
To not render header or a footer for a specific page (controller or action or both) you can make the rendering conditional:
application.html.erb
<body>
<% unless controller_name = "static_pages" %>
<%= render "layouts/header" %>
<% end %>
<%= yield %>
<% unless action_name = "landing_page" %>
<%= render "layouts/footer" %>
<% end %>
</body>
Alternatively as a more sophisticated approach you can create a separate layout file for a specific controller: https://guides.rubyonrails.org/layouts_and_rendering.html#finding-layouts

Rails: How to make small changes in different views

Is it possible to make small changes in different views?
The same partial is rendered in index.html.erb and show.html.erb as below.
index.html.erb
<%= render #schedules %>
show.html.erb
<%= render #schedules %>
What I'd like to do is not to display some value in the index.html.erb. (and display some value in both erb)
For example, I'd like to display start_at and end_at only in show.html.erb and display title in both erb.
_schedule.html.erb
<% schedule.rooms.each_with_index do |a, idx| %>
<% a.events.each do |e| %>
<%= l(e.start_at) %>-<%= l(e.end_at) %> # display only show.html.erb
<%= e.title %> #display both erb
...
<% end %>
...
<% end %>
Althogh I come up with idea which I create two partials, it contradicts the DRY policy.
It would be appreciated if you could give me any idea.
You can use controller.action_name.
<% if controller.action_name == 'show' %>
<%= l(e.start_at) %>-<%= l(e.end_at) %> # display only show.html.erb
<% end %>
The params hash also contains the action_name.
action_name is enough and do the trick but personally I don't like this. I'd do two separate partials.
Can check current action and current controller on page. So we can call single partial from different actions and can customize as per action name or action and controller name.
eg.
<% schedule.rooms.each_with_index do |a, idx| %>
<% a.events.each do |e| %>
<% if #current_controller == "events" and #current_action == "show" %>
<%= l(e.start_at) %>-<%= l(e.end_at) %> # display only show.html.erb
<% end %>
<%= e.title %> #display both erb
...
<% end %>
...
<% end %>
Also need to update Application Controller.
class ApplicationController < ActionController::Base
before_filter :instantiate_controller_and_action_names
def instantiate_controller_and_action_names
#current_controller = controller_name
#current_action = action_name
end
end
You could use CSS to hide/show the content based on context.
In practice, I have found this a good way to reuse partials that have small differences. Especially when those differences don't cost anything to compute i.e. printing a date
You can cache the partials without worrying about where they are rendered
Reduce conditional logic
Remove duplication
<% if controller.action_name == 'show' %> is fine for a simple use case. If/When you come to have multiple places where the partial needs to be rendered, it will become unwieldy. The CSS solution would only require another wrapper <div class="schedules--whatever"> and the related CSS style.
show.html.erb
<div class="schedules--show">
<%= render #schedules %>
</div>
index.html.erb
<div class="schedules--index">
<%= render #schedules %>
</div>
_schedule.html.erb
<% schedule.rooms.each_with_index do |a, idx| %>
<% a.events.each do |e| %>
<div class="event__date">
<%= l(e.start_at) %>-<%= l(e.end_at) %>
</div>
<%= e.title %>
...
<% end %>
...
<% end %>
schedules.css
.schedules--show .event__date {
display: block;
}
.schedules--index .event__date {
display: none;
}

Change title tag for a controller in Rails

In my application.html.erb I have <title>MySite</title> in my <head>. But on the users#show pages I want to have <title><%= user.name %></title>.
What is the Railsy way to override this?
You should use content_for in users/show.html.erb:
<% content_for :title do %>
<%= user.name %>
<% end %>
Then in your layout you can do this:
<title>
<% if content_for? :title %>
<%= yield :title %>
<% else %>
MySite
<% end %>
</title>
You may wish to use an external gem called meta-tag-helpers:
#app/views/layouts/application.html.erb
<head>
<%= meta_tags %>
</head>
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_meta_tag
private
def set_meta_tag
set_meta title: "MySite"
end
end
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find params[:id]
set_meta title: #user.name
end
end
This is very similar to the content_for recommendation of fivedigit, but a much more robust & rounded solution. A Caveat here is the meta_tags helper will populate all your meta tags - including the title etc
You can use the controller_name and action_name parameter methods to check which controller is being called and which action the controller is using to choose which title to show. You can try
<% if controller_name == "users" && action_name == "show" %>
<title><%= user.name %></title>
<% else %>
<title>MySite</title>
<% end %>
Note that the conditional will short circuit if the controller is not the users controller.
You can find more on this in the rails guides.
Hope this helps!

How to call a controller method to populate partial on root page?

I've got a controller method:
def latest_blogs
#latest_blogs = BlogEntry.all.order('dato DESC').limit(4)
end
A root html.erb file which acts as my home page:
<div class="body-box">
<div class="body-content">
<%= render :partial => 'blog_list' %>
</div>
</div>
The blog_list partial:
<div class="blog-list">
<%= latest_blogs.each do |blog| %>
<%= render :partial => "blog_preview", locals: {blog: blog} %>
<% end %>
</div>
And the blog_preview partial which is just a stub for now:
<div class="blog-preview">
<%= blog.title %>
</div>
My routes.rb entries:
resources :blog_entries
root :to => 'home#blog_home', :as => 'root'
How can I tell my app when it loads the root page to present latest_blogs? Thanks.
I would start with: this method should not be in the controller. Put it in a model instead
class BlogEntry < ActiveDirectory::Base
def self.latest (n = 4)
all.order('dato desc').limit(n)
end
Then just use it as:
BlogEntry.latest
Note that this method will also be available on relations:
user.blog_entries.latest(1) #=> returns last user blog entry
You can declare the latest_blog as helper method. Please try the following:
class BlogsController < ApplicationController
def latest_blogs
#latest_blogs = BlogEntry.all.order('dato DESC').limit(4)
end
helper_method :latest_blogs
end
Is latest_blogs your actual controller action, or is it just some method you're exposing to the view? If it's just some method you're exposing then you should make it a helper method.
def latest_blogs
#latest_blogs ||= BlogEntry.all.order('dato DESC').limit(4)
end
helper_method :latest_blogs
Note that I changed the = to ||= in here. I think that's better in this case so that if you happen to call the helper multiple times then it won't re-evaluable it repeatedly.
And FYI, you can also clean up your markup code a little bit. Your root html file could do:
<div class="body-box">
<div class="body-content">
<div class="blog-list">
<%= render partial: 'blog_preview', collection: latest_blogs %>
</div>
</div>
</div>
Then _blog_preview.html.erb:
<div class="blog-preview">
<%= blog_preview.title %>
</div>

Resources