Issue iterating records - ruby-on-rails

I'm sure this is a stupid question, but I've got a todo list app I've built in Rails. There are projects and projects have many tasks. Aside from seeing projects with their tasks, I also wanted to see a simple listing of all of the tasks. The index action in the tasks controller looks like this:
def index
#tasks = Task.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #tasks }
end
end
And in views/tasks/index.html.erb I simply had this to start:
<%= #tasks.each do |t| %>
<%= t.title %>,
<% end %>
But when I look at /tasks, I get this:
Task 1, Task 2, Task 3
#<Task:0x103225138>#<Task:0x1031ea998>#<Task:0x1031ea858>
I can't figure out why the
#<Task:0x103225138>#<Task:0x1031ea998>#<Task:0x1031ea858>"
...are appearing or how to keep them from appearing. They appear even if I'm not printing anything other than the loop code. Any ideas? Thanks!

<% #tasks.each do |t| %>
<%= t.title %>,
<% end %>
Will fix that. The problem is that you are printing with = the result of .each which returns the array it was called on, in this case #tasks. So your code is effectively doing this:
<% #tasks.each do |t| %>
<%= t.title %>,
<% end %>
<%= #tasks %>
A side note:
A much nicer way to do this is simply
<%= #tasks.map(&:title).join ", " %>

Related

Merging three Active Record arrays

I'm trying to merge three Active Record arrays in a Rails 5 app so that I have a nice collection of jobs, forum threads and blogs on my home page.
I have the following code:
application_controller.rb
def home
#blogs = Blog.limit(6)
#jobs = Job.where(approved: true).limit(6)
#forum_threads = ForumThread.includes(:forum_posts).limit(6)
#everything = #blogs + #jobs + #forum_threads
end
home.html.erb
<% #everything.sort_by(&:created_at).reverse.each do |item| %>
<% if item.is_a?(Job) %>
<%= render partial: "application/partials/home_job", locals: {item: item} %>
<% elsif item.is_a?(ForumThread) %>
<%= render partial: "application/partials/home_forum", locals: {item: item} %>
<% elsif item.is_a?(Blog) %>
<%= render partial: "application/partials/home_blog", locals: {item: item} %>
<% end %>
<% end %>
The problem I'm having is that this code doesn't display the records in date order by created_by, instead I have a rather random collection of jobs, forum threads and blogs starting at a seemingly random date.
If I add, say, a new job, it doesn't appear in the collection displayed on /home page. However, if I delete all records from the db and start adding new records then the code works fine and displays the posts in the correct order with the behaviour I expect.
I can't push this code live to Heroku because I can't delete all the records that already exist in production. It's almost like there's some kind of cache that needs clearing out. Does anyone know what's going on?
#blogs = Blog.order(created_at: :desc).limit(6)
etc.
Problem 1: Getting the right records from the database
Option A: If you will always be sorting each model by the created_at value (a common desire), add a default_scope to each model (Rails 4+ version below). Your limit calls in the controller will automatically take advantage of the default scope.
app/models/blog.rb
class Blog < ActiveRecord::Base
default_scope { order created_at: :desc }
...
end
Option B: If you only do this in certain circumstances, but you do it for several models, I like to extract that into a Timestamped module (below). You will need to use the most_recent method in your controller when extracting records from the database to ensure you're getting the most recent ones.
app/models/concerns/timestamped.rb
module Timestamped
extend ActiveSupport::Concern
included do
scope :most_recent, -> { order created_at: :desc }
scope :least_recent, -> { order created_at: :asc }
scope :most_fresh, -> { order updated_at: :desc }
scope :least_fresh, -> { order updated_at: :asc }
end
end
class Blog < ActiveRecord::Base
include Timestamped
...
end
Problem 2: Sorting the array
Even with a simple case like this, I'd recommend adding an array extension that matches the most_recent method that timestamped.rb defines for ActiveRecord::Relations.
lib/array_extensions.rb
class Array
def most_recent
sort { |a, b| b.created_at <=> a.created_at }
end
end
and then require the extension with an initializer:
config/initializers/extensions.rb
require 'array_extensions'
Problem 3: Keeping the controller clean.
Generally each controller action should only set up one instance variable, and in this case it looks like you are not even using the #blogs, #jobs, and #forum_threads variables in the views. Vivek's answer solves this, although I'd do the flattening and sorting logic in the controller:
def home
#posts = Blog.most_recent.limit(6) + Job.approved.most_recent.limit(6) + ForumThread.most_recent.includes(:forum_posts).limit(6)
#posts = #posts.most_recent
end
Problem 4: Minimize if/then logic in your view
Instead of this:
<% #everything.sort_by(&:created_at).reverse.each do |item| %>
<% if item.is_a?(Job) %>
<%= render partial: "application/partials/home_job", locals: {item: item} %>
<% elsif item.is_a?(ForumThread) %>
<%= render partial: "application/partials/home_forum", locals: {item: item} %>
<% elsif item.is_a?(Blog) %>
<%= render partial: "application/partials/home_blog", locals: {item: item} %>
<% end %>
<% end %>
Do this:
<% #everything.sort_by(&:created_at).reverse.each do |item| %>
<%= render "application/partials/home_#{item.class.name.underscore}", item: item %>
<% end %>
And make sure your partials are named appropriately
You can do like this:
def home
#collections=[]
#collections << Blog.limit(6)
#collections << Job.where(approved: true).limit(6)
#collections << ForumThread.includes(:forum_posts).limit(6)
end
<% #collections.flatten.sort_by(&:created_at).reverse.each do |item| %>
....iteration here ....
<% end %>
if i understood your question correctly, you want to sort the array after you merged it by date. I would do it like that:
#everything = #everything.sort {|x| x.created_at }
Hope that helps.

Render Partial Item only if column is true

I'm trying to loop over my 'offers' collection in a partial, but each 'offer' has a column 'featured' which is a boolean which defaults to false. I'm trying to loop over the collection and only display the offers which have the featured column set to true.
I currently have:
<%= render #offers %>
Trying below but comes back with 'undefined method 'featured'
<%= render #offers if #offer.featured == true %>
Any help would be fantastic
In your controller, set up another collection:
#featured_offers = Offer.where(featured: true)
And render that instead:
<%= render #featured_offers %>
To correct your immediate code, you're calling .featured on #offer - which doesn't exist.
You'll either need to loop through #offers and use logic on offer, or use conditions inside the partial (which is highly inefficient):
<% #offers.each do |offer| %>
<%= render offer if offer.featured %>
<% end %>
or
<%= render #offers %>
#_offer.html.erb
<% if offer.featured %>
This is super inefficient
<% end %>
--
#jason is correct with his recommendation of using a where clause
You may even want to go a step further and set up a scope:
#app/models/offer.rb
class Offer < ActiveRecord::Base
scope :featured, -> { where featured: true }
end
#offers = Offer.featured
You can even chain the scope:
#offers = Offer.where(user_id: params[:id])
<%= render #offers.featured %>

will_paginate links not showing in the browser or in console

I just installed will_paginate and it seems to be working fine except is not displaying the links at the bottom of the page. Here is my controller:
def index
#projects = Project.paginate(:page => params[:page], :per_page => 9)
#user = User.new
respond_to do |format|
format.html
format.json { render json: #projects }
end
end
and this is in my index.html.erb:
<% will_paginate #projects %>
<% #projects.each do |project| %>
<div class ="col-sm-3 project">
<div id="project-index-pic">
<%= link_to image_tag(project.url), project_path(project), class: "project-index-pic" %>
</div>
// .....more stuff ....
<%end%>
As I said, it seems to be working as far as displaying the proper amount of results (9) on the first page, and if I change that to 2 or 3 it responds accordingly. I am just not getting the links at the bottom of the page to take me to the next 9 results. In other threads people seem to see html elements associated with the will_paginate but I don't. I ran the troubleshooting lines in console:
[1] giving_tree(main)> defined? WillPaginate
=> "constant"
and:
[2] giving_tree(main)> ActiveRecord::Base.respond_to? :paginate
=> true
So that looks good. Checked the docs and a couple threads and I just don't see what I am missing. Thanks!
It's the little things.
<% will_paginate #projects %>
should have been:
<%= will_paginate #projects %>

Exit rails gracefully if conditions are met?

I have a code where I would need to exit (php version of die), if certain events occur
Snippet in trace_controller.rb
def show
rule=Rule.new
#order,#order_error=rule.get_order(#order_external_id)
#order_items, #order_items_error=rule.get_order_items(#order)
#order_item_units, #order_item_units_error=rule.get_order_item_units(#order_items)
#outbound_messages, #outbound_messages_error = rule.check_outbound_messages(#order_external_id)
#inbound_messages, #inbound_messages_error = rule.check_inbound_messages(#outbound_message)
......
In show.html.erb
<% unless #order_error.blank? %>
<%= #order_error.html_safe %>
<% else %>
<%= render "trace/display_tabular_data", :data => #order %>
<% end %>
.....
.....
<% unless #order_items_error.blank? %>
<%= #order_items_error.html_safe %>
<% else %>
<% #order_items.each do |order_item| %>
<h5>Order Item</h5>
<%= render "trace/display_tabular_data", :data => order_item %>
<% end %>
<% end %>
......
Most of my functions are dependent on outcome of previous functions. Now take get_order_items function which is dependent on orders. If the order does not exist, there is no need to calculate get_order function as it won't exist either. Additionally, it fires up an error, as it says I am passing it a NIL object when I perform operations on orders inside get_order_item.
Additionally in the show.html.erb - #order_items, and #order_items_error should not even exist if the order does not exist. I just wanna render the function till the order_error, and then stop.
Now, coming from PHP background, I forgot that rails does not have die. So is there an alternate of die? Abort isn't it. I need it to exit disgracefully. Or is my best shot using conditionals if, unless etc...But it will look ugly as the page will become full of them. How would you about it?
To cut off the current action and render the view, you can use return. This will crash with errors if you try to use an unitialized instance variable in the view though.
You can also use render nothing: true which stops the current action and renders nothing.
Perhaps consider redirecting back as well:
flash[:error] = 'There was no order!'
redirect_to :back
I am not sure what exactly you want to do. But this should meet with what you are asking for.
def show
rule=Rule.new
#order,#order_error=rule.get_order(#order_external_id)
unless #order.blank?
#order_items, #order_items_error=rule.get_order_items(#order)
#order_item_units, #order_item_units_error=rule.get_order_item_units(#order_items) unless #order_items.blank?
end
#outbound_messages, #outbound_messages_error = rule.check_outbound_messages(#order_external_id)
#inbound_messages, #inbound_messages_error = rule.check_inbound_messages(#outbound_message) unless #outbound_messages.blank?
end
Similarly, you can add conditions in your view file:
<% unless #order.blank? %>
<% unless #order_error.blank? %>
<%= #order_error.html_safe %>
<% else %>
<%= render "trace/display_tabular_data", :data => #order %>
<% end %>
.....
.....
<% unless #order_items.blank? %>
<% unless #order_items_error.blank? %>
<%= #order_items_error.html_safe %>
<% else %>
<% #order_items.each do |order_item| %>
<h5>Order Item</h5>
<%= render "trace/display_tabular_data", :data => order_item %>
<% end %>
<% end %>
<% end %>
.......
.......
<% end %>
It is just a basic idea what I get from your example, though it was not clear enough to show what you want to achieve. So, whole idea is to check the variable before using it!
One more point I would like to mention, avoid using unless - else, unless should be used alone, if you need to put some logic in else block, why not use if - else. That probably makes more sense than unless - else.

Complex Rendering in Rails

For about a week now I have been trying to get a view to render. I have an application that needs to be able to export collections so I decided to use a line partial that renders as a .txt and .csv in the web browser. So far so good in terms of getting the entire collection to render (line by line). However, I am having trouble getting certain collection objects (in this case products) to duplicate themselves based on a certain attribute (size element).
The code below is kind of where I am stuck at now
Controller
class PexportController < ApplicationController
layout 'csv'
def index
end
def show
#feed_template = params[:id]
#products = Product.find :all
#products.each do |product|
unless product.size.nil? || product.size.empty? || product.size.kind_of?(Fixnum)
#products << new_products_for(product)
end
end
respond_to do |format|
format.html
format.text
end
end
private
def new_products_for(product = {})
products = Array.new
product.size.each do |p|
products << Product.new(p.attributes)
end
products
end
end
View
<%= render partial: 'pexport/p', collection: #products %>
Partial
<%= p.sku %> <%= p.name %> <%= p.price %> ......
I basically just need to get the controller method to work. The attribute :size that I am using for the line duplicator is simply an array like so [1,2,3]. And I would like products that contain this size attribute to duplicate themselves based on the number of sizes in their size array. I am not even sure if I am going about it the right away but it has gotten to that point where I am going in circles so I figured I would post it.
Alternative answer: is there some reason you need to duplicate the entire object in the controller? You could simplify things by just doing something like this in your view:
<% if p.size.is_a?(Array) %>
<% p.size.each do |s| %>
<%= p.sku %> <%= p.name %> <%= p.price %> <%= s %>
<% end %>
<% else %>
<%= p.sku %> <%= p.name %> <%= p.price %> <%= p.size %>
<% end %>
Or something to that effect.
If I understand what you're doing, you have a list of products, but some of those product entries should be displayed as more than one product if they have more than one size. Assuming that's correct, your logic is a bit off: new_products_for is returning an array which is being added as a single element at the end of your #products array. So your partial won't know how to deal with it. You could try something like this:
#my_products = Product.find :all
#products = []
#my_products.each do |p|
if p.size.blank? || p.size.kind_of?(Fixnum)
#products << p
else
#products += new_products_for(p)
end
end
Also, I suggest you make the Product.new line more explicit:
products << Product.new(:sku => p.sku, :name => p.name, ...)
p.attributes will give you all the attributes of the model, including id, created_at, updated_at which may interfere with what you're doing.

Resources