DRY layouts and views - ruby-on-rails

I have a user profile page with a sidebar. I need to create more pages within the profile. For example, edit password, edit profile information, statistics, purchase history list, etc. I'm not sure how to proceed while keeping things DRY. I'm trying to get everything to be the exact same except the main content. While going through some tutorials I came across yield but it was mostly used in the application.html.erb to render navigation, footer, etc. I don't understand how to use it for "sub-views".
The way I'm doing it right now seems wrong.
Routes:
as :user do
# Routes to change password of signed in user
get 'user/password' => 'users/registrations#edit_password', as: 'edit_password'
# Routes to change user profile information of signed in users
get 'user/profile' => 'users/registrations#edit_profile', as: 'user_profile'
end
Views:
views\users\show.html.erb:
views\users\registrations\edit_profile.html.erb:
views\users\registrations\edit_password.html.erb:
All contain this 1 line
<%= render 'users/shared/profile' %>
views\users\shared\profile:
<%= render 'users/profile/sidebar' %>
<!-- Display Profile or Password based on route -->
<% if current_page?(user_path current_user) %>
<!-- User Profile -->
<%=render 'users/profile/adminPanels' %>
<% elsif current_page?(edit_password_path) %>
<!-- Password Reset -->
<%=render 'passwordForm' %>
<% else %>
<!-- Profile Edit -->
<%= render 'users/registrations/profileForm' %>
<% end %>
Basically what I wanted to do is keep all the surrounding layout but change the rendered content. Now that I need to add more, extending this if statement really seems like the wrong way to go.

Yeah this is definitely not the way to go, but it's good that you recognize that so no worries. As you guessed, the way to do this involves using layouts and yield. You can read about yield in this Rails guide.
While you can have a layouts like application.rb that your entire Rails app uses by default, you can also define layouts nested within this layout. This is described in the same Rails guide as above, towards the bottom.
This way, the stuff that is the same for your entire application is defined in the application layout, the stuff that is same for everything that is a user profile is defined in the users layout, and the stuff that is specific to each view is defined in there.
Side note: as the users layout is in the layouts folder, I acted as though you moved the _sidebar partial there as well since it is really a partial that belongs to the layout and should be near it.
views/layouts/users.html.erb
<%= render '_sidebar' %>
<%= yield :users_content %>
<%= render template: 'layouts/application' %>
views/users/show.html.erb
<% content_for :users_content do %>
put the view code specific to users/show here
<% end %>
views/users/registrations/edit.html.erb
<% content_for :users_content do %>
put the view code specific to editing a user registration here
<% end %>
etc.
The only thing that you may have issue with is that Rails is using the name of the controller to match the nested users layout and that may break for the registrations stuff if that's a different controller. You can fix that by explicitly calling render template: 'layouts/users' inside of those controller actions.

From what code snippet you've provided, the DRYest way would be to move
<%=render 'users/profile/adminPanels' %>
directly to the show.html.erb page after rendering shared/profile. Same thing for other views.

Related

would it be ok to create an instantiate an object in another controller?

I have a controller for customer. In the new action, I redirect to another page, which belong to the pages controller
class CustomersController < ApplicationController
def new
redirect_to register_path
end
Would it be possible to create the object in the registration action like this?
class PagesController < ApplicationController
def registration
#customer = Customer.new
end
end
I believe the setup is something like this: you have models in your application for a Customer and, say, an Agent. Your website users register as either and the desire is to have a single HTML page (URL) with both options available. They choose which one they are and submit some fields, say name/email/password. To keep it simple, without bothering with JavaScript to hide things behind tabs, you have something like:
**Customer**
Name: ___________
Email: __________
Password: _______
[Submit]
**Agent**
Name: ___________
Email: __________
Password: _______
[Submit]
You have a few options here to avoid your guilty feeling in the Rails controllers:
Go heavy client-side JavaScript. Don't have the new actions on the controllers. The JavaScript creates the page elements. The create action becomes a JSON API endpoint, thereby avoiding the problem in the Ruby application. This is obviously a significant architectural deviation from where I think you are today.
Use a little bit of JavaScript to dynamically load the correct 'partial' into the DOM when the user switches between the options. Avoids the underlying problem in the question by effectively separating the 'pages' out to the two controllers. The Pages→registration action does not need to set any instance variables for the view. The JavaScript deals with the partial loading. (see 'link_to' and the 'remote' option)
Don't include both forms in the same HTML page, just default to one, say the Customer one, and provide a link to navigate to the Agent one, e.g. a link in a tab, or a plain link like "Not a Customer? Register as an Agent." In this scenario, you have a neat mapping to the Ruby MVC design, each of the pages are just the new action of its relevant controller. The downside is a page load to change between the two options. This is the simplest, plainest choice … if you can get the boss to agree to the UX. PS: if you are using turbolinks, then the 'feel' of this option in the browser will be not far from option (2).
Stick to your design
Keep in mind that you will have difficultly dealing with error conditions and messages with option (4). You can do it, but the code won't be simple or easy to maintain.
If option (4) is a must, one simplification can be the create actions on each of the controllers rendering their own new in case of an error. If you submit the 'Agent' form from your starting page, with errors, to the Agents→create action, that action finishes with a render 'new' to show the user the Agents→new page. No 'customer' form is visible. You could then add a sprinkle of option (3) in there with a "Not an Agent? Register as a Customer." link under the form. Doing this greatly simplifies your error handling.
Which then leads to a suggestion for your original problem. Cheat. Don't have an #customer instance variable for the new actions (or the registration action). Use partials for the customer and agent forms, and pass in a new object to form_for, e.g.
pages/registration.html.erb
<%= render 'customers/new_form' %>
<%= render 'agents/new_form' %>
customers/new.html.erb
<%= render 'customers/new_form' %>
customers/_new_form.html.erb
<% form_for Customer.new do |f| %>
<%# include the inputs shared with the edit action %>
<%= render 'fields', f %>
<%= f.submit %>
<% end %>
customers/_fields.html.erb
<%# 'f' is one of the locals passed to the partial %>
<% f.input_field :name %>
<% f.email_field :email %>
<% f.password_field :password %>
customers/edit.html.erb
<% form_form #customer do |f| %>
<%= render 'fields', f %>
<%= f.submit %>
<% end %>
… then you would follow the same pattern for:
agents/new.html.erb
agents/_new_form.html.erb
agents/_fields.html.erb
agents/edit.html.erb

Rails convention - placing logic in view vs controller vs partial

Messages are displayed green if sent by the current user, and blue otherwise. Following Rails convention, where does that logic belong?
Introdution
The user will visit /group/:id to see the list of messages, so the corresponding view is views/groups/show.html.erb and the corresponding controller is controllers/groups_controller.rb.
The message we want to display are in an array in #group, as #group.messages. The array is sorted by timestamp.
The code to style the color of the message is not important, but for simplicity purposes we will say there are two class selectors (one for from and one for to) and we can simply add a class attribute to the div that a message is within to change its color.
Both the user's sent and received messages are held in the array #group.messages.
If we have an individual message stored in message, we can test if it was sent by the current user with:
if session[:user_id] == message.user_id
Problem
Messages are ordered by timestamp and will need to be displayed in that order. For this reason, I can't see any clean way of handling the logic in the controller.
I would like to keep as much logic as possible out of the views and especially out of the partials, but after considering the options for rendering sent and received messages in different ways, the cleanest option I've found is to put the logic in the message partial.
Handling the logic in the message partial:
<% if message.user.id == session[:user_id] %>
<div class="to">
<p> <%= message.body %> </p>
</div>
<% else %>
<div class="from">
<p> <%= message.body %> </p>
</div>
<% end %>
Pros:
This method handles the logic with one if statement that is clean and simple
It allows us to make the code DRY because we won't have to use the logic anywhere else if we want it on other pages
Since every message only has a body, we don't have to make another partial to display messages without this formatting
Cons:
The logic is in the partial! I think people I'm working with or other programmers or even myself would first look in the controller then in the view then in the partial to make any changes or see the code
This doesn't feel like normal Rails convention
Handling the logic in the view:
Possibly two clean solutions -
1) Style the messages inside the logic or
2) Render a different partial for sent/received messages
Styling inside the logic:
<% #group.messages.each do |message| %>
<% if message.user.id == session[:user_id] %>
<div class="to">
<p> message.body </p>
</div>
<% else %>
<div class="from">
<p> message.body </p>
</div>
<% end %>
<% end %>
Rendering different partials:
<% #group.messages.each do |message| %>
<% if message.user.id == session[:user_id] %>
<%= render :partial => '/messages/sent_message', :message => message %>
<% else %>
<%= render :partial => '/messages/received_message', :message => message %>
<% end %>
<% end %>
Pros:
Either view solution keeps the logic out of the partial
It makes sense that showing something as one color or another is decided in the view
The view solution using two partials is clean and allows us to avoid styling within logic which also means that we can change the style within the partials and affect the look of messages everywhere.
Cons:
Both view options mean that our code is no longer DRY. Using these methods will mean that if we want the same functionality on 3 other pages, we will have to write the same code 3 more times
It makes sense that a view shouldn't be deciding anything
The view solution using two partials means that we will crowd the views/messages folder with partials, and still not have a default partial for rendering messages
Both of the view solutions just feel dirty in my opinion
My main points about my solutions -
No option allows for the logic to be held within the controller
Placing the logic inside the view means that to provide the same functionality on multiple pages, the same code will be written in more than one place
The option that looks the cleanest and makes the most sense to me means putting logic inside a partial, and there must be a better way.. right?
None of the solutions seem like they follow Rails convention
Which of the three options I coded best follow Rails convention?
Is it possible to place the logic in the controller?
Is there a better way to design this so that there is a clear solution following Rails convention?
What you probably have realized is that each of the three versions you described is either not DRY or not scalable. You've done a great job analyzing pros and cons of each option, so there is very little for me to add there. :)
To add presentation functionality to your models, Rails community uses Presenters. There is a great article on Presenters here that explains more about them.
Basically, you'll want to have one partial for message:
<div class=<%=#presenter.css_class%>>
<p> <%= message.body %> </p>
</div>
Then Presenter:
class MessagesPresenter
def initialize(message, current_user)
#message = message
#current_user = current_user
end
def css_class
message.user == current_user ? 'to' : 'from'
end
private
attr_reader :message, :current_user
end
And controller:
#presenter = MessagesPresenter.new(#message, current_user)
Voila! The presenter is available in both views and partials and is a great place to stash all presentation logic.
Since the only difference in these examples in the CSS class, you're repeating yourself quite a bit. Can't you add or remove a class on the tag depending on whether the tag belongs to the current_user or not?
This is really a presentation issue, and you can handle this simple logic for displaying the correct CSS tag using decorators (http://johnotander.com/rails/2014/03/07/decorators-on-rails/). I recommend using Draper (https://github.com/drapergem/draper).
First, for simplicity, add a current_user helper method to application_controller.rb to return the authenticated user.
Add a Decorator:
MessageDecorator.rb
def recipient_class
user_id == current_user.id ? "to" : "from" # (user_id delegates to message object)
end
Now your views can have much cleaner logic
Views
Message Partial:
<div class="<%= message.recipient_class %>">
<p><%= message.body %></p>
</div>
collection partial in the main view:
<%= render partial: "message", collection: #messages, as: :message %>
Finally, call decorate on messages in your controller action:
#messages = #group.messages.decorate
EDIT
You can also use a simple helper method rather than a decorator:
def css_class_for_message(message)
message.user_id == current_user.id ? "to" : "from"
end

how do i place this ruby code correctly?

I have a piece of code which displays a complicated 'listing' of resources. by complicated i mean that it has name, date, time posted, number of comments, picture, description, and tons more things that you obviously only want to write that block of code one time.
I'm trying to think of how this would go into the rails architecture and I'm at a loss
i need to display the resource listing using a different variable in different controllers. currently its in a partial called _resource_view.html.erb
This is fine for the main listing, but its not usable for the persons profile listing, which takes the same format but shows different resources
_resources_expanded.html.erb
<ul class="res-list">
<% #resources.each do |resource| %>
... list stuff
<% end %>
</ul>
It uses #resources on index, but on the profile page it uses the same listing code except its #user.resources.each do |resource|
I was thinking maybe something like this but this seems redundant .. and im not even sure if it will work
<ul class="res-list">
<% #resources.each do |resource| %>
<%= render 'layouts/resources_expanded' %>
<% end %>
</ul>
Don't use instance variables, use local variables in the partial - that lets you pass in a single each time through the #resources.each loop.
<%= render 'layouts/resources_expanded', :resource => resource %>
and then in _resource_view.html.erb change #resource to resource

Design pattern for side bar with dynamic content in Rails

I would like to have a right side bar with content changes for each page.
For example, when I am in Friends page, the side bar should display New Friends.
When I am in Account page, the side bar should display Recent Activities.
How should I go about this to respect Rails design patterns? I heard about Cells gem, but I am not sure if I use it.
here is one way, in your layout add a named yield section
<div id="main-content">
<%= yield %>
</div>
<div id="side-content">
<%= yield(:side_bar) %>
</div>
Then in your views put content into the named yield using content_for
# friends view ....
<% content_for(:side_bar) do %>
<%= render :partial => "shared/new_friends" %>
<% end %>
# account view ....
<% content_for(:side_bar) do %>
<%= render :partial => "shared/recent_activity" %>
<% end %>
this requires you to be explicit about what content appears in the side bar for every view,
maybe having it do it dynamically is better? probably depends on the specific situation and your preference
see also - http://guides.rubyonrails.org/layouts_and_rendering.html#understanding-yield
I came by this question in a moment of a big design change in our views. After thinking about the sidebar problem a bit, I realized that there's no best solution (as always). There are better solutions for each case.
I'll compare 3 solutions here:
using content_for(:sidebar) and yield(:sidebar)
using the partials approach
using the Cells gem
1. Using content_for(:sidebar) and yield(:sidebar)
This is good for cases when each link (each controller action) you access renders a different sidebar. In this case, each view you access will have the content_for(:sidebar) part.
If your sidebar view depends only on the state of some variable in the session, for example, the sidebar should not be rendered for every link you access.
Then you should use a good caching system like turbolinks, to avoid rendering many times the same thing, or use something like the Cells gem with a javascript to render only the main part of the layout.
2. Using partials
Using partials is always good to eliminate duplication. If your sidebar is very simple and is changed for every controller, you can render it as a partial. But if you're rendering different partials in the same controller, according to some state, it may be an indication that you have business logic in your views, which should be avoided.
3. Using the Cells gem
Very good design pattern when you have to render your sidebar from a different controller than the rest of the view each time.
It takes a lot of business logic out of the view, which sure is a good practice.
Here you have an action calling a view. Inside that view, there is a statement render_cell(:sidebar, params). This statement will do some business logic and render the view of the sidebar. It's as if the first action called other controller actions to render specific parts of your view (called cells)
If you make changes to the sidebar only, you may have to create other simple action, so that a javascript will request it. This action will call the render_cell(:sidebar) method again to respond with the view.
It's a very interesting approach.
Other ideas:
Your sidebar could be rendered only with javascript from the same
action.
Your sidebar could be rendered by an angular controller, and rails sends jsons with the sidebar objects. (look for "One page apps")
try something like this
<div class="sidebar">
<% if current_page?(controller => "friends", :action => "show") %>
<h4>New Friends</h4>
<% elseif current_page?(controller => "accounts", :action => "show") %>
<h4>Recent Activities</h4>
<% end %>
</div>
If the above code fits what you are trying to do(looks like this is what you want to achieve), then stick with it, else it may be beneficial to go with some gems. Also checkout helper page on how to use current_page? method. Hope it helps

How can I use content_for to put something in :yield

I am in ruby 1.9.2, rails3.
So My website has some structures,
and I want to put menu in a middle of my webpage.
I am doing something like (within application.html.erb file)
blahblahblah
<div id="menu">
<%= yield :menu %>
<div>
blahblhablah
I have a file menu.html.erb which has menu structure for the site.
What can I do if I want to use a file within ./layout folder to be used to be part of that yield :menu? I was wondering, if I have to use content_for for every controller, and within every functions...
Btw, menu.html.erb will be different for each controller, so thats why I am yielding it.
In conclusion, I just want to include one common shared menu.html.erb pretty much everywhere.
You could do something like this in your views:
<% content_for(:menu) do %>
<%= render :partial => "/layouts/user_menu.html.erb" %>
<% end %>
You could try to combine this with controller.controller_name (not sure this works for Rails3) and load a different menu for each controller automatically.
You might consider watching the railscast on layouts, it's concise and helpful.
Numbers 7 and 8.
http://railscasts.com/episodes?search=layout

Resources