I am building an application where n users can talk to each other (like a messaging application) in public. Because you might want to have a different bio for each talk you do (for example a discussion about me regarding Ruby on Rails would need a different bio than one about Psychology) I have a Spkr model which has a User and a Tlk. The below code successfully means that on the users profile page, for each instance of them being a Spkr, the Tlk, and it's participants is visible with each Spkr's image (so if a Tlk has three participants, then all three images will be visible).
The setup is such where the default image is the User's image, but the Spkr can also customise their image by uploading one as a Spkr. I am worried that I am loading the front end with too much computation. Right now everything works... so is it ok? Or should I be limiting the computation happening when building views?
Thank you
<% #user.spkrs.each do |spkr| %>
<%= link_to show_tlk_path(spkr.tlk) do %>
<h4><%= spkr.tlk.title %></h4>
<% spkr.tlk.spkrs.each do |speaker| %>
<div class="tlk-tlking-image spkr-image image-spkr-<%= spkr.id %>"
<% if speaker.image.present? %>
style="background-image: url(<%= rails_blob_url(speaker.image) %>)"
<% elsif speaker.user.image.present? %>
style="background-image: url(<%= rails_blob_url(speaker.user.image) %>)"
<% end %>
>
</div>
<p><%= speaker.name %></p>
<% end %>
<% end %>
<% end %>
It tends to be considered good practice to keep the view as free of 'back end' calculations as possible. These files are often worked on by front end developers who may not even know how to code ruby, so the less of it that is in the view the better. It's also just not where it belongs in rail's Model Controller View framework.
First of all the code you've put can be simplified to:
<% #user.spkrs.each do |spkr| %>
<%= link_to show_tlk_path(spkr.tlk) do %>
<h4><%= spkr.tlk.title %></h4>
<% spkr.tlk.spkrs.each do |speaker| %>
<div class="tlk-tlking-image spkr-image image-spkr-<%= spkr.id %>"
style="background-image: url(<%= rails_blob_url((speaker.image || speaker.user.image) %>)"
>
</div>
<p><%= speaker.name %></p>
<% end %>
<% end %>
<% end %>
But as you say, if you want to handle this in a more appropriate place, I'd add a method to the Speaker class:
# app/models/speaker.rb
class Speaker << ApplicationBase
def image_for_view
image || user.image
end
end
This will let you call speaker.image_for_view which I think reads nicely in the view file itself.
Along with the great answer let me just add something that might help you to make views more clear. Might not be relevant to your question directly but might help you to get some idea how you can make views beautiful.
The first thing to make views look good are helpers. Though rails provide helpers for every controller, helpers are global meaning it can be used anywhere in any views. So, global formatings should be done with helpers. Like if you want a date formatter that needs to be used in a lot of view files, you can create a helper called date_helper.rb in app/helpers and put you desired date formatting -
module DateHelper
def formatted_date(date)
date.strftime([%m/%d/%Y')
end
end
Next is what rails people like to call a Presenter pattern. This is helpful when you don't want some logic to be shared across all views. Some logic that doesn't feel like belongs in controller or model are put there to make views readable. Suppose you have a view like below which is a bit messy -
<p>
Post title: <%= post.title.gsub("forbidden word", "") %>
<%= link_to "Read post", post, class: "w-75 p-3 text-#{post.draft? ? "orange" : "green"} border-#{post.draft? ? "orange" : "green"}" %>
</p>
To make this more beautiful you can create a presenter class named post_presenter.rb which should reside in app/presenters and write some code like -
class PostPresenter
def initialize(post)
#post = post
end
def title_without_forbidden_words
#post.title.gsub("forbidden word", "")
end
def css_color
#post.draft? ? "orange" : "green"
end
end
and in the view -
<% presenter = PostPresenter.new(post) %>
<p>
Post title: <%= presenter.title_without_forbidden_words %>
<%= link_to "Read post", post, class: "w-75 p-3 text-#{presenter.css_color} border-#{presenter.css_color}" %>
</p>
Such way a view might be more clear and also it can be lifesaver for frontend developers. This are the best two methods I found till now that makes a rails view beautiful which I always try to use.
Examples are taken from rubyguides website. Thanks to them,
Related
I have a #story record with the following attributes:
author
author_title
year
source
source_link
I'm rendering it in the view so it comes out like this:
James Joyce (author), 1882, Wikipedia
I am hoping there is a less convoluted way to generate the DOM for the citation than this (which is imperfect, as I explain below):
<%= #story.author %><% if !#story.author_title.blank? %> (<%= #story.author_title %>)<% end %><% if !#story.year.blank? %>, <%= #story.year %><% end %><% if !#story.source_link.blank? %>, <%= link_to #story.source, #story.source_link, target: "_blank" %><% end %>
As none of the fields are mandatory, the if-field-not-nil-then-you-may-need-a-comma issue is what I suspect could be handled more elegantly. For example, if author is blank, then I don't want to display the author_title or the trailing comma.
You can try the below code
create a two helper method
def author_story(author)
[#story.author_title, #story.year].compact.join(',')
end
def author_link
link_to(#story.source_link, text: 'testing')
end
and in view
<% if #story.author.present? %>
<div>
<span>
<%= author_story(#story) %>
</span>
<span>
<%= author_link(#story) %>
</span>
</div>
<% end %>
See my comments, Decorators is the way to go. You'll learn to love it and never go back.
If you think it is too much overhead for only a one-time simple task... then create a model method for this:
class Story < ApplicationRecord
.... #other stuff
def author_with_title_and_year
"#{author} #{author_title}, #{year}".squish
end
end
And add the link manually behind it:
<p><%= #story.author_with_title_and_year %> <%= link_to source, source_link %></p>
I am making a book review/online book group app.
On the individual book show page, I would like to have the user decide whether or not they would like to see reviews, reviews with spoilers or all of it. Therefore when the page loads they should see only the main info about the book, and three buttons: See Reviews, See Spoilers, See All.
Now this seems like it should be a totally easy task. If I were using JS and jQuery, I would just hide all the elements and have the buttons trigger jQuery shows if they match the objects spoiler code, however this does not work in Rails.
I have tried doing js in the view, I have tried running the logic through their own routes, I've tried functions in the controller and helper and nothing works!! Auuugghh! I've been working on this for almost 2 days and want to smash my computer in. Granted, this is my first project back on Rails after 6 months off, I've been working mostly in Node, and my Rails is rusty. Here's the code that may be pertinent, but it's Rails so it's all over the place so it might just be easier to look at the github repo, which is here. https://github.com/nwimmer123/readit_rails
I've been cruising StackOverflow and reading APIDock, but haven't been able to figure this out. Any help would be greatly appreciated.
show.html.erb
<div class="container">
<%= button_to "Show Reviews", class: "btn btn-primary col-xs-2" %>
<%= button_to "Show Spoilers", class: "btn btn-primary col-xs-2" %>
<%= link_to "Show It All", controller: "books", method: "find_reviews", class: "btn btn-primary col-xs-2"%>
</div>
<% if #show_the_reviews == true %>
<div class="container">
<% #reviews.each do |review| %>
<% if review.book_id.to_s == params[:id].to_s %>
<div class="review">
<div>
<strong><%= review.user.name %></strong>
<% if review.spoiler == "1" %>
<span class="center-text">SPOILER</span>
<% end %>
</div>
<p><%= review.body %></p>
<button class="pull-right">Respond</button>
</div>
<% end %>
<% end %>
</div>
<% end %>
</div>
books controller
def show
#user = current_user
#reviews = Review.all
end
private
def book_params
params.require(:book).permit(:title, :author, :genre, :image, :publication_date, :publisher, :synopsis)
end
def current_book
#book = Book.find_by_id(params[:id])
end
def find_reviews
puts "LOOK HERE!!!!!!"
#reviews.each do |review|
if review.book_id.to_s == params[:id].to_s
puts review.spoiler
end
end
#show_the_reviews = true
redirect_to book_path(#book.id)
end
end
When I click Show It All, it redirects to home page?!
Note, I realize this question is poorly written and vague, but I'm super annoyed and can't figure out how to write it better. I think I'm looking for basic strategies here.
You can do it easily with jQuery, just give the reviews an appropriate class..
<% review_class = review.spoiler == true ? ' spoiler' : ' spoiler-free' %>
<div class="review <%= review_class%>">
Then put some <script> at the top of the view that will toggle show/hide for $('.spoiler') and $('.spoiler-free') and have your buttons onclick call the js functions.
You can do it in pure rails by making a field-less form with submit buttons, and in the called action test params[:commit] which will have the text of the button that was clicked, and then use that in conditional logic to filter out whether you retrieve spoiler reviews, non-spoiler reviews, or both. But the jQuery is way more responsive.
Im trying to implement Social links to my rails app but im starting to realized its going to be a bit repetitive. I want the appropriate icon to appear if a link is provided by user.
Here is what I have so far .
<% unless #user.github == nil %>
<%= link_to #user.github , class: 'btn btn-social-social btn-github' do %>
<span class="fa fa-github"></span>
<% end %>
<% end %>
Here the Github icon will show up only if github its not nil . How can i do this to multiple links and at the same time keeping my Code DRY ?
You could define a helper for that:
def social_icon_helper(user, service)
if user.respond_to?(service) && !user.send(service).nil?
link_to user.send(service), class: "btn btn-social-social btn-#{service}" do
content_tag(:span, class: "fa fa-#{service}")
end
end
end
Then in your view:
<p>
<%= social_icon_helper(#user, :github) %>
</p>
or even
<% %i(github facebook twitter).each do |service| %>
<%= social_icon_helper(#user, service) %>
<% end %>
UPDATE
Sorry, please find updated helper code. Please note that I added a " #{service}" after tag definition.
def social_icon_helper(user, service)
if user.respond_to?(service) && !user.send(service).nil?
link_to user.send(service), class: "btn btn-social-social btn-#{service}" do
content_tag(:span, " #{service}", class: "fa fa-#{service}")
end
end
end
That helper produces the following link for me:
You'll only have a few of them, right? ~5 or so? In this case, I wouldn't bother too much about keeping the code DRY. The code for different links is different enough to make it not worth the effort (different object properties, css classes, etc.)
You have shown only one case (github), but I can easily imagine that for some cases the property and the css class will not match (for example, user.google_plus and fa-gplus, or something)
Just repeat the code 5 times and hide it in a partial.
If you insist, then #retgoat's answer should do.
I have two different plans (plan with an ID of 1 and plan with an ID of 2). I've created a partial for each to show on the home page based on which plan the user is logged in as. I would like to create two different index pages for each plan to be directed to.
Plan ID 1 users need to be directed to an index of users with a plan ID of 2, and plan ID 2 users need to be directed to an index of users with a plan ID of 1. Here's the part of the code that controls this feature. How can I create an index feature which sends plan ID 1 and plan ID 2 user to different pages after click on the relative partial?
pages/home.html.erb
<div class="col-md-6">
<% if current_user.plan.name == "mentor" %>
<%= render partial: "pages/mentor" %>
<% else %>
<%= render partial: "pages/mentee" %>
<% end %>
</div>
pages/_mentee.html.erb
<div class="well">
<h2 class="text-center">Mentor Community</h2>
<h4 class='text-center'>Get the support you need.</h4>
<br><%= link_to "Find a Mentor", "#", class: 'btn btn-default btn-lg btn-block' %>
</div>
pages/_mentor.html.erb
<div class="well">
<h2 class="text-center">Mentee Com</h2>
<h4 class='text-center'>Give the support that's needed.</h4>
<br><%= link_to "Find a Mentee", "#", class: 'btn btn-default btn-lg btn-block' %>
</div>
Do this:
#config/routes.rb
root "pages#home"
resources :plans, only: :show #-> url.com/plans/1
#app/controllers/plans_controller.rb
class PlansController < ApplicationController
def show
#plan = Plan.find params[:id]
end
end
#app/views/plans/show.html.erb
<% #plan.users.each do |user| %>
<%= user.name %>
<% end %>
after click on the relative partial
You could send users to the specific plan page by using the following:
#app/views/pages/home.html.erb
<% #plans.each do |plan| %>
<%= link_to plan.id, plan %>
<% end %>
There is so much more you need to consider, but for now, the above should help you get an understanding on the overall structure.
Let me explain a little on how you need to adapt your thinking (this might seem off topic, but will help you profusely, I guarantee it)...
The above is how Rails is meant to work -- it takes a request from the user, matches it to a controller action, and then populates it with model data.
The correct way for Rails to work is with something called object orientated programming - each time you initiate an action / request, it has to invoke & manipulate objects of data.
Whilst this may seem complicated, the sooner you get your head around it, the quicker you'll be able to make much more intricate rails applications.
--
Your question implies that you've not considered the full potential of a data-driven rails application.
Not that it matters, but if you changed your approach so that you got rid of pages and instead had plans with users, you'd be able to have as many plans as you required.
That is the correct way to think about programming (to make a system, not just a quick fix), which you can then use to expand the functionality as you need.
A good way to remove the conditional would be using the plan name to find the partial. It would be something like this
<div class="col-md-6">
<%= render partial: "pages/#{current_user.plan.name}" %>
</div>
But you need to make sure that each plan has its correct partial file. Otherwise you'll get a rendering error, because the partial isn't found.
I'm a little new to back-end programming...I'm currently running the following code in my rails 4 app to show a basic list of all the admins on a project (if there are any)...
<% if project.projectadmins.any? %>
<div class="row-fluid">
<% project.projectadmins.each do |user| %>
<div class="collaborator">
<%= link_to user do %>
<%= image_tag user.image_url(:thumb).to_s, :class => "profile-pic-thumb" %>
<% end %>
</div>
<% end %>
</div>
<% end %>
However, I also have projectcollaborators for each project, so I'd like to know what the most effective way would be to combine those and provide a list of both projectadmins AND projectcollaborators (with project admins being listed first if there are any...other than that ordering is not important).
I assume the if statement at the beginning would change to...
<% if project.projectadmins.any? || project.projectcollaborators.any? %>
but I'm not 100% sure and am lost on the rest...any help is much appreciated.
You could create a scope, for example project_admins_and_collaborators, which gets all the needed records and then use it in your loop.
You can also do this in following way
project_admins_and_collaborators = project.projectadmins
project_admins_and_collaborators << project.projectcollaborators
project_admins_and_collaborators.flatten.uniq do |user|
#your code
end