I am on rails 4 trying to build a simple helper to reduce some of the code in my view.
Here is the view code (show.html.erb) before using a helper:
<% unless #article.long_effects.blank? %>
<ul>
<% #article.long_effects.split(';').each do |effect| %>
<li><%= effect %></li>
<% end %>
</ul>
<% end %>
and here is the helper I built for the above code:
def list(attribute)
unless attribute.blank?
content_tag(:ul) do
attribute.split(';').each do |a|
content_tag(:li, a)
end
end
end
end
which I then call from the view like so
<%= list(#article.long_effects) %>
Unfortunately, the helper is not returning anything. Any suggestions? This is my first time writing a helper that returns HTML, so maybe I am doing something wrong? Thank you for the help.
from
def list(attribute)
unless attribute.blank?
content_tag(:ul) do
attribute.split(';').each do |a|
content_tag(:li, a)
end
end
end
end
to
def list(attribute)
unless attribute.blank?
content_tag(:ul) do
attribute.split(';').each do |a|
concat content_tag(:li, a)
end
end
end
end
concat method will be useful to join the collection object from looping conditions.
Related
I'd like to check for a url param in an if statement, but I don't know the proper syntax or if this is even possible.
so far I got
<% if param(order: :top) %>
<% elsif param(order: :top && live: :true) %>
both of which I know are wrong.
The url params I'm trying to check for are:
www.url.com/?live=true&order=top
or
www.url.com/?order=top
index.html.erb
<% current_user.following_channels.each do |c| %>
<% if param(order: :top) %>
<% c.discussions.where('created_at > ?', 1.days.ago).each do |discussion| %>
<% elsif param(order: :top && live: :true) %>
<% c.discussions.where('live = ?', true).each do |discussion| %>
<% end %>
Some more code...
<% end %>
Your code doesn't make much sense to me, anyway:
if params[:order] == 'top'
if params[:live] == 'true'
c.discussions.where(live: true).each do |discussion|
.
.
.
end
else
c.discussions.where('created_at > ?', 1.days.ago).each do |discussion|
.
.
.
end
end
end
Also, as you learn Rails you should move all that logic to the controller and model.
EDIT: Moving logic to controller (untested code, it may fail):
def index
#channels = current_user.following_channels.includes(:discussions)
#channels = #channels.where('discussions.created_at > ?', 1.days.ago) if params[:order] == 'top'
#channels = #channels.where(discussions: {live: true}) if params[:live] == 'true'
end
end
index.html.erb:
<% #channels.each do |channel| %>
<div class="channel-container">
<h2><%= channel.name %></h2>
<ul>
<% channel.discussions.each |discussion| %>
<li><%= discussion.text %></li>
<% end %>
</ul>
</div>
<% end %>
That way, the view knows nothing about how to get data, it just presents it to user.
You still can move logic from controller to the models, but get comfortable with all of this first.
params["live"].nil? #this will return false if there is no url parameter called "live"
params["live"].blank? # this will return false if url parameter live is blank
So on, for the other parameters. As CAmador has said, you need to move your codes mostly to controller(in this case of dealing with params, the code should go to controller).
I have a loop that looks like this
<% #user.collections.each do |collection| %>
<h1 class="impact"> <%= collection.name %><br></h1>
<%= collection.stories.count %>
<% end %>
It works perfectly to show the Collections that belongs to a User, and then show how many Stories are in each Collection.
However, I want to use a helper that does this.
in the view
<% #user.collections.each do |collection| %>
<h1 class="impact"> <%= collection.name %><br></h1>
<%= number_of_stories_in_collection %>
<% end %>
in the helper
module CollectionsHelper
def number_of_stories_in_collection
collection.stories.count
end
def render_stories_count
if number_of_stories_in_collection.zero?
'No stories in this collection yet'
else
"#{number_of_stories_in_collection} #{'story'.pluralize(number_of_stories_in_collection)}"
end
end
end
I get an error that says
undefined method `stories' for #<Collection::ActiveRecord_Relation:0x007f510f504af8>
Any help is appreciated, thanks!
The 'collection' variable isn't an instance variable, so the helper can't see it.
Change your view to this:
<% #user.collections.each do |collection| %>
<h1 class="impact"> <%= collection.name %><br></h1>
<%= number_of_stories_in(collection) %>
<% end %>
And your helper method to:
def number_of_stories_in(collection)
collection.stories.count
end
This way you are passing the variable to the helper correctly.
extending #Richard's answer and little bit of optimisation to avoid n+1 queries..
<% #user.collections.includes(:stories).each do |collection| %>
<h1 class="impact"> <%= collection.name %><br></h1>
<%= render_stories_count(collection) %>
<% end %>
helper:
module CollectionsHelper
def number_of_stories_in(collection)
collection.stories.length
end
def render_stories_count(collection)
if (count = number_of_stories_in(collection)).zero?
'No stories in this collection yet'
else
"#{count} #{'story'.pluralize(count)}"
end
end
end
I am trying to implement search functionality in my rails app where I search and display a particular search result first on my index.html.erb view. At the moment I have a search function working and it returns the particular item on its own on the index page.
Ideally I would like to have this item displayed first and then all the other items to display below.
My code is as follows:
brand.rb
def self.search(query)
where("author like ?", "%#{query}%")
end
brand_controller.rb
def index
if params[:search]
#brand = Brand.search(params[:search]).order("created_at DESC")
else
#brand = Brand.all.order(':date')
end
end
I know the where method returns the value as an array so I could probably use array.first to output this result first but is there an easier way to output my desired view. Thanks!
So turned out to be a pretty simple solution, I blame mondays.
All I had to do was create another variable for my search and iterate that result first, then iterate through the rest of the items.
in my controller
def index
#brand = Brand.order('created_at DESC')
if params[:search]
#brand = Brand.search(params[:search]).order("author DESC")
#other = Brand.search_all(params[:search]).order("author DESC")
else
#brand = Brand.all.order('author DESC')
end
end
In my model
def self.search(query)
where("author like ?", "%#{query}%")
end
def self.search_all(query)
where("author not like ?", "%#{query}%")
end
and finally in my view
<% if #brand.any? %>
<% #brand.in_groups_of(2) do |group| %>
<% group.each do |brand| %>
<% if brand %>
<h4> <%= brand.author %></h4>
<a href="<%=brand_path(brand)%>">
<%=image_tag brand.brand_logo, class: 'img-rounded', :"data-uid" => brand.uid %> </a>
<% end %>
<% end %>
<% end %>
<% end %>
<% if #other %>
<% #other.in_groups_of(2) do |group| %>
<% group.each do |other| %>
<% if other %>
<h4> <%= other.author%></h4>
<a href="<%=brand_path(other)%>">
<%=image_tag other.brand_logo %> </a>
<% end %>
<% end %>
<% end %>
<% end %>
In this case which pattern will be faster?
Obviously Pattern1 with helper looks much more sophisticated and looks clean.
But it send SQL every time when user_link method is called.
Here it calls up to 100times at one page loading.
Which way would be better for benchmark performance?
Pattern1. With helper
application_helper
def user_link(username)
link_to User.find_by_username(username).user_profile.nickname, show_user_path(username)
end
view
<% #topics.order("updated_at DESC").limit(100).each do |topic| %>
<%= user_link(topic.comment_threads.order("id").last.user.username) if topic.comment_threads.present? %>
<% end %>
Pattern2. Without helper. Just only view
<% #topics.order("updated_at DESC").limit(100).each do |topic| %>
<%= link_to(topic.comment_threads.order("id").last.user.nickname, show_user_path(topic.comment_threads.order("id").last.user.username) ) if topic.comment_threads.present? %>
<% end %>
try
# Topics model
#via scope
scope :get_topic_list, lambda do
order("updated_at DESC").joins(:comment_threads => :user).limit(100)
end
#via method
def self.get_topic_list
Topic.order("updated_at DESC").joins(:comment_threads => :user).limit(100)
end
# in your controller or move to model itself (recommened)
#topics = Topic.get_topic_list
# in you view
<% #topics.each do |topic| %>
<%= link_to(topic.comment_threads.order("id").last.user.nickname, show_user_path(topic.comment_threads.order("id").last.user.username) ) if topic.comment_threads.present? %>
<% end %>
I've been away from Rails for a while now, so maybe I'm missing something simple.
How can you accomplish this:
<%= yield_or :sidebar do %>
some default content
<% end %>
Or even:
<%= yield_or_render :sidebar, 'path/to/default/sidebar' %>
In the first case, I'm trying:
def yield_or(content, &block)
content_for?(content) ? yield(content) : yield
end
But that throws a 'no block given' error.
In the second case:
def yield_or_render(content, template)
content_for?(content) ? yield(content) : render(template)
end
This works when there's no content defined, but as soon as I use content_for to override the default content, it throws the same error.
I used this as a starting point, but it seems it only works when used directly in the view.
Thanks!
How about something like this?
<% if content_for?(:whatever) %>
<div><%= yield(:whatever) %></div>
<% else %>
<div>default_content_here</div>
<% end %>
Inspiration from this SO question
Try this:
# app/helpers/application_helper.rb
def yield_or(name, content = nil, &block)
if content_for?(name)
content_for(name)
else
block_given? ? capture(&block) : content
end
end
so you could do
<%= yield_or :something, 'default content' %>
or
<%= yield_or :something do %>
block of default content
<% end %>
where the default can be overridden using
<%= content_for :something do %>
overriding content
<% end %>
I didn't know you could use content_for(:content_tag) without a block and it will return the same content as if using yield(:content_tag).
So:
def yield_or_render(content, template)
content_for?(content) ? content_for(content) : render(template)
end