How do I manage if-then explosion in view files? - ruby-on-rails

I apologize if this doesn't follow good question guidelines, but I hope it's well in class with How to Manage CSS Explosion and receives a similarly helpful response.
I'm familiar with some basic view prolixity mitigation strategies such as the following:
Use helpers where appropriate
Don't repeat yourself
Use partials and layouts
Feel free to suggest something if I'm missing some big idea in the above list.
Nevertheless, I still end up with having several dimensions/degrees of freedom in my view, causing a lot of if-then statements or at least ternary blocks. For instance, in something I'm currently messing with, I'm working on a header bar for a program where the view is called when three "big" variables:
Whether the user is admin
Whether the user is logged in
Whether the page being viewed belongs to the user or someone else
It ends up looking like this mess:
<% content_for :subheader do %>
<div class="row">
<% if #user %>
<% if #user == current_user %>
<%= link_to 'My programs', user_programs_path(current_user), :class => 'active' %>
<% else %>
<%= link_to "#{#user.username}'s programs", user_programs_path(#user), :class => 'active' %>
<% end %>
<%= link_to 'Browse all programs', programs_path %>
<% else %>
<% if current_user %>
<%= link_to 'My programs', user_programs_path(current_user) %>
<% end %>
<%= link_to 'Browse all programs', programs_path, :class => 'active' %>
<% end %>
<%= link_to 'New Program', new_program_path, :class => 'admin' if current_user.admin? %>
</div>
<% if #regions %>
<div class="row second">
<%= link_to 'Regional program search', request.fullpath, :class => 'active' %>
</div>
<% end %>
<% end %>
Ugly. Readable and easily accessible, but ugly. Some suggestions?
Between experience and new technologies like LESS, I've become pretty good at slimming down my CSS files, but I'm still running into explosion issues with my MVC views.

I would use helpers and model definitions to dry up your code:
class User
def possesive
self == current_user ? 'My' : "#{username}'s"
end
end
module ...Helper
def user_program_link user
if user
link_to "#{user.possesive} programs", user_programs_path(user), :class => 'active'
elsif current_user
link_to 'My programs', user_programs_path(current_user)
end
end
end
You can then simplify all the if statements for the user_program_path calls to this:
<%= user_program_link #user %>
Which would reduce your view code to:
<% content_for :subheader do %>
<div class="row">
<%= user_program_link #user %>
<% if #user %>
<%= link_to 'Browse all programs', programs_path %>
<% else %>
<%= link_to 'Browse all programs', programs_path, :class => 'active' %>
<% end %>
<%= link_to 'New Program', new_program_path, :class => 'admin' if current_user.admin? %>
</div>
<% if #regions %>
<div class="row second">
<%= link_to 'Regional program search', request.fullpath, :class => 'active' %>
</div>
<% end %>
<% end %>
Continue this process to DRY up the rest of your code as well.

Rewrite the view code as follows:
<% content_for :subheader do %>
<div class="row">
<% if #user || current_user %>
<%= link_to ((current_user == #user or #user.nil?) ? "My programs" :
"#{#user.username}'s programs"),
user_programs_path(#user || current_user),
:class => 'active' %>
<% end %>
<%= link_to 'Browse all programs', programs_path,
:class => (#user ? '' : 'active') %>
<%= link_to 'New Program', new_program_path, :class => 'admin' if current_user.admin? %>
</div>
<% if #regions %>
<div class="row second">
<%= link_to 'Regional program search', request.fullpath, :class => 'active' %>
</div>
<% end %>
<% end %>

Related

How to pass current_item id in partial after link_to in RoR

I am trying to make a front end from which if clicked on a button "Rent", it will render a Modal of a bootstrap in which I need to get current_item in order to rent that item. I am currently getting the latest item instead of current_item.
_itemviewer.html.erb
<% #items.each_with_index do |item, j| %>
<% if item.user != current_user %>
<%= link_to 'Rent', '#rentModal', class: 'btn btn-success', "data-toggle" => "modal", "data-target" => "#rentModal"%>
<%= render partial: "layouts/rent_modal", locals: {current_item: item } %>
<% end %>
<% end %>
inside _rent_modal.html.erb
<%= simple_form_for [current_item, #acquiretime] do |f| %>
<div class = "add_item_form">
<%= f.input :required_time, as: :date, html5: true %>
<%= f.input :return_time, as: :date, html5: true %>
<%= f.hidden_field :user_id, value: current_user.id %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<%= f.submit 'Add Item', class: 'btn btn-primary' %>
</div>
<% end %>
the problem is that you are creating many links to your modal with the same id.
<% #items.each_with_index do |item, j| %>
<% if item.user != current_user %>
<%= link_to 'Rent', '#rentModal', class: 'btn btn-success', "data-toggle" => "modal", "data-target" => "#rentModal"%>
<%= render partial: "layouts/rent_modal", locals: {current_item: item } %>
<% end %>
<% end %>
here's it, for each item you create a new link to your modal with the id #rentModal the last modal is the one of the latest item; that may be the cause of being it rendered.
so you may need to create different modals and links ids for each item.
like the code below.
<% #items.each_with_index do |item, j| %>
<% if item.user != current_user %>
<%= link_to 'Rent', "#rentModal_#{j}", class: 'btn btn-success', "data-toggle" => "modal", "data-target" => "#rentModal_#{j}"%>
<%= render partial: "layouts/rent_modal", locals: {current_item: item, index: j } %>
<% end %>
<% end %>
here you can see that we used j to define the Modal id, you need to use that id also in your modal html part, so in your _rent_modal.html.erb you need to use the same id for each item and that is why we passed index as a local to that partial.. Also you need to wrap your code inside _rent_modal.html.erb inside a div with the modal id. you can see how you define a modal here.

More efficent way to write this if statement? (Clean up code)

Originally, the idea was if the user had written a bio, show it on their profile. Otherwise, display a message.
<% if #user.bio.present? %>
<p class="user-bio"><%= #user.bio %></p>
<% else %>
<% if #user == current_user %>
<p class="user-bio">Press the green edit button and write something interesting about yourself.</p>
<% else %>
<p class="user-bio">Sorry, this user hasn't written anything about themselves yet.</p>
<% end %>
<% end %>
Then I thought of a way to improve this feature. Instead of making the user navigate to their settings page (devise/registrations/edit), let them edit their bio straight from their profile page (users/show).
To do this, the bio will be an input field that just looks like text, but when hovered over or clicked on, the user can see they can edit the text.
So I made this mock up version below and added resources to my user controller. But I get an error with the last else tag. I'm new to rails (and ruby) and I think this code is quite messy. Could someone with more experience help me write a better if statement that solves the same problem. I need a cleaner solution.
<% if #user.bio.present? %>
<p class="user-bio"><%= #user.bio %></p>
<% else %>
<% if #user == current_user %>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, multipart: true }) do |f| %>
<%= devise_error_messages! %>
<div class="input-group">
<%= f.text_field :bio, class: "form-control" %>
</div>
<div class="actions">
<%= f.submit "Update", class: "btn-signin" %>
</div>
<% else %>
<p class="user-bio">Sorry, this user hasn't written anything about themselves yet.</p>
<% end %>
<% end %>
You don't need to nest a new if block inside of the first else.
Also, as #JoeC pointed out in the comments, you need a closing <% end %> for your form_for.
<% if #user.bio.present? %>
<p class="user-bio"><%= #user.bio %></p>
<% elsif #user == current_user %>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, multipart: true }) do |f| %>
<%= devise_error_messages! %>
<div class="input-group">
<%= f.text_field :bio, class: "form-control" %>
</div>
<div class="actions">
<%= f.submit "Update", class: "btn-signin" %>
</div>
<% end %>
<% else %>
<p class="user-bio">
Sorry, this user hasn't written anything about themselves yet.
</p>
<% end %>
If you think this is still too much tag soup, you could put the form inside of a partial named _form.html.erb and render it. That way the main view template is focused on the logic of what to display rather than getting into the details of the form.
<% if #user.bio.present? %>
<p class="user-bio"><%= #user.bio %></p>
<% elsif #user == current_user %>
<%= render 'form' %>
<% else %>
<p class="user-bio">
Sorry, this user hasn't written anything about themselves yet.
</p>
<% end %>
The last thing that I'll point out is that you may want to switch around the first and second if tests. The way your logic is laid out, the user would never be able to edit their bio after they've entered one.
I agree 100% with Chris Peters. You can place the form code into a separate file called partial:
app/views/YOUR_CONTROLLER_NAME/_form.html.erb
<%= form_for(resource, as: resource_name, url: registration_path(resource_name),
html: { method: :put, multipart: true }) do |f| %>
<%= devise_error_messages! %>
<div class="input-group">
<%= f.text_field :bio, class: "form-control" %>
</div>
<div class="actions">
<%= f.submit "Update", class: "btn-signin" %>
</div>
<% end %>
Some information about partials you can find here: http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials
If you decide to follow Chris's advice, you can transform the code further:
<% if #user.bio.present? %>
<p class="user-bio"><%= #user.name %></p>
<% else %>
<%= render 'form' if #user == current_user %>
<p class="user-bio">
Sorry, this user hasn't written anything yet.
</p>
<% end %>
Ruby If statements examples: Great Ruby Shorthands For If…Then…Else
You can also consider using content tags as below:
<% if #user.bio.present? %>
<%= content_tag(:p, #user.name, class: "user-bio") %>
<% else %>
<%= render 'form' if #user == current_user %>
<% no_bio_msg = "Sorry, this user hasn't written anything yet." %>
<%= content_tag(:p, no_bio_msg, class: "user-bio") %>
<% end %>
Hope this will help.

Rendering a partial inside a block

I'm new to rails. I have this block in my view afrikaans.html.erb
<% #afrikaans.each do |course| %>
<div class="course">
<h3 class="course-name"><%= link_to course.name, course.url %></h3>
<% if I18n.locale == I18n.default_locale %>
<p class="course-description_en"><%= course.description_en %></p>
<% else %>
<p class="course-description_se"><%= course.description_se %></p>
<% end %>
<% if course.youtube_url.blank? == false %>
<p><%= raw ApplicationHelper.youtube_embed(course.youtube_url) %></p>
<% end %>
<% if course.language_id == 1 %>
<p> <%= image_tag("eng.png", :alt => "England", :class =>"round") %></p>
<% else %>
<p> <%= image_tag("swe.png", :alt => "Sweden", :class =>"round") %></p>
<% end %>
<% if ApplicationHelper.active_link?(course.url) == false %>
<td><%= I18n.t('home.broken_link') %></td>
<% end %>
<p><%= course.nbr_of_votes %> <%= I18n.t('home.votes') %></p>
</tr>
<% end %>
I also have another file swahili.html.erb with the same structure. I wanted to implement something like this
afrikaans.html.erb
<% #afrikaans.each do |course| %>
<%= render 'shared/partial' %>
<% end %>
So that I can also have
swahili.html.erb
<% #swahili.each do |course| %>
<%= render 'shared/partial' %>
<% end %>
The partial will contain the part of the block. I've tried this but it's not working. My question is this even possible in rails and if so what could be the problem. What options do I have if it isn't possible so that I can avoid the repetition since the two files have the same structure?
Update. This One worked out for me. I only needed to add :course => course on the block so that my views becomes something like
<% #afrikaans.each do |course| %>
<%= render 'shared/course_body', :course => course %>
<% end %>
Of course I've not named my partial "partial". This was just a matter of asking. Thanks to one #Alexander Panasyuk's answer.
Just create shared directory within your app/views path. And create file _partial.html.erb inside shared:
<div class="course">
<h3 class="course-name"><%= link_to course.name, course.url %></h3>
<% if I18n.locale == I18n.default_locale %>
<p class="course-description_en"><%= course.description_en %></p>
<% else %>
<p class="course-description_se"><%= course.description_se %></p>
<% end %>
<% if course.youtube_url.blank? == false %>
<p><%= raw ApplicationHelper.youtube_embed(course.youtube_url) %></p>
<% end %>
<% if course.language_id == 1 %>
<p> <%= image_tag("eng.png", :alt => "England", :class =>"round") %></p>
<% else %>
<p> <%= image_tag("swe.png", :alt => "Sweden", :class =>"round") %></p>
<% end %>
<% if ApplicationHelper.active_link?(course.url) == false %>
<td><%= I18n.t('home.broken_link') %></td>
<% end %>
<p><%= course.nbr_of_votes %> <%= I18n.t('home.votes') %></p>
</tr>
Then render your partial in afrikaans.html.erb like that:
<% #afrikaans.each do |course| %>
<%= render 'shared/partial', :course => course %>
<% end %>
or in swahili.html.erb:
<% #swahili.each do |course| %>
<%= render 'shared/partial', :course => course %>
<% end %>
It is most definitely possible, and usually a good idea.
In the future it would be nice if you could post the actual results and/or error messages you get, which would help a lot when trying to help you.
That said, I'm guessing you need to pass the course variable to your partial. Change
<%= render 'shared/partial' %>
to
<%= render 'shared/partial', :course => course %>
Partials do not have access to local variables in other partials. In that sense you can think of each partial as separate methods on the same object instance.
<%= render 'shared/partial', locale: 'swahili', course: course %>
You will have local vars 'locale' and 'course' in your partial set to 'swahili' and #course, respectively. Also, I'd advise to name your partials something more meaningful, like 'course'.

Ruby on Rails: one check_box for several submit_tag

I need to have one check_box for several purposes.
For example: I have a list of files. User can choose some of them to be deleted or analysed.
I have the following code but it accepts only one submit_tag "Delete selected".
<% if #files%>
<%= form_tag destroy_multiple_files_path, method: :delete do %>
<%= submit_tag "Delete selected" %>
<% #files.each do |file| %>
<% if (arraydb.file=="no") %>
<p><td> <%= check_box_tag "files[]", file.id %></td><%= file.name %></p>
<% else %>
<div class="my_profile_info">
<p><td> <%= check_box_tag "files[]", file.id %></td> <%= file.name %></p>
<td class="Info">
Info
</td>
</div>
<% end %>
<%end%>
<%end%>
<%else%>
<%end%>
I would like to have submit_tag "Analyse" as well.
I tried something like this but of course it did not work.
<% if #files%>
<%= form_tag destroy_multiple_files_path,analyse_multiple_files_path method: :delete,method:post do %>
<%= submit_tag "Delete selected" %>
<%= submit_tag "Analyse" %>
<% #files.each do |file| %>
<% if (arraydb.file=="no") %>
<p><td> <%= check_box_tag "files[]", file.id %></td><%= file.name %></p>
<% else %>
....
routes.rb:
resources :files do
collection do
delete 'destroy_multiple'
end
end
controller:
def destroy_multiple
#files = File.find(params[:files])
#files.each do |item|
item.destroy
end
end
Thanks in advance.
You can indeed have multiple submit buttons, you just have to give them names:
<%= submit_tag "Delete selected", :name => 'delete' %>
<%= submit_tag "Analyse", :name => 'analyse' %>
You can then check what the commit param contains in the controller and act accordingly:
if params[:commit] == 'delete'
# delete things
elsif params[:commit] == 'analyse'
# analyse things
end
The rest of the form will be submitted as usual.
this worked for me:
<%= form_tag destroy_multiple_files_path, method: :get do %>
<%= submit_tag "Delete selected", :name => 'delete' %>
<%= submit_tag "Analyse", :name => 'analyse' %>
controller:
if params[:commit] == 'Delete selected'
# delete things
elsif params[:commit] == 'Analyse'
# analyse things
end
routes.rb:
resources :files do
collection do
get :destroy_multiple
end
end

In which controller should I place elements that persist throughout the application?

Everything after #posts should persist throughout the application (Like the left sidebar you find on Facebook).
def index
#title = "Posts"
default_order = "content_changed_at DESC"
params[:order_by] ||= default_order
#posts = current_user.subscribed_posts.paginate(:page => params[:page],
:per_page => 5,
:order => params[:order_by])
#subscribed_tags = current_user.subscribed_tags
#recent_posts = current_user.posts.limit(5).order("created_at DESC")
#tags= Tag.limit(20).order("ID asc")
#user = current_user
end
views/layout/_sidebar.html.erb:
<div class="user-profile">
<% avatar = image_tag(current_user.avatar.url(:thumb), :class => "authenticated-avatar") %>
<%= link_to avatar, "/users/#{current_user.id}" %>
<%= link_to "#{current_user.username}", "/users/#{current_user.id}", :class => "authenticated-username" %>
</div>
<%= form_for(#user, :remote => true) do |f| %>
<div class="field">
<%= f.label :subscribed_tag_names %><br />
<%= f.autocomplete_field :subscribed_tag_names, autocomplete_tag_name_posts_path, :"data-delimiter" => ' ', :class => "autocomplete_field" %>
</div>
<div class="actions">
<%= f.submit :class => "user_subscribed_tag_names_submit" %>
</div>
<% end %>
<div class="user-subscribed_tags">
<% #subscribed_tags.each do |subscribed_tag| %>
<%= link_to "#{subscribed_tag.name}(#{subscribed_tag.posts.count})", unsubscribe_tags_path(:unsubscribed_tag_name => subscribed_tag.name) %>
<% end %>
</div>
<div class="user-recent-posts">
<h4>Recent Posts</h4>
<ul>
<% #recent_posts.each do |recent_post| %>
<li><%= link_to recent_post.title, recent_post %></li>
<% end %>
</ul>
</div>
<div class="top-tags">
<% #tags.each do |tag| %>
<span class="tag-name"><%= tag.name %></span>
<span class="tag-count"><%= tag.posts.count %></span>
<% end %>
</div>
<% end %>
Where should I place the code in the controller if I want them to persist throughout the application? (I would like to see some example code if possible).
If it persists throughout the application, i.e. for every actions of all controllers, you might place it in a before_filter in the application controller.
Add a before_filter to the application controller as :
before_filter :find_recent_posts_and_tags
And define it (as private) :
private
def find_recent_posts_and_tags
# define your instance variables
end
More about filters here and here.

Resources