Complex Rendering in Rails - ruby-on-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.

Related

search form in nav bar doesn't render anything

I have a search form in my nav bar
<%= simple_form_for :query, url: clients_products_path, method: :get, wrapper: :inline_form, html: {class: 'form-inline'} do |f| %>
<%= f.input :keyword, placeholder: "Recherche" %>
<%= f.submit "Valider" %>
<% end %>
In my product_controller.rb
class Clients::ProductsController < ApplicationController
def index
filter_products if params[:query].present?
#products ||= Product.all
end
private
def filter_products
return if params[:query].blank?
#products = Product.where('lower(title) LIKE ?', params[:query][:keyword]) if params[:query][:keyword].present?
end
end
My query seems to be correct as I can find product in the rails console.
but it doesn't display anything in the product#index...
Where am I wrong?
update
All products are well displayed, and everything disapear when I make a query
clients/products/index.html.erb
<% #products.each do |product| %>
<%= link_to clients_product_path(product) do %>
<%= image_tag(product.attachments.first.url) %>
<%= product.title %>
<%= product.price %>
<% end %>
<% end %>
here is the result
http://localhost:3000/clients/products?utf8=%E2%9C%93&query%5Bkeyword%5D=jean&commit=Valider
I believe your issue lies here:
#products = Product.where('lower(title) LIKE ?', params[:query][:keyword])
You need to either prepend, append or wrap your query with %. For example:
#products = Product.where('lower(title) LIKE ?', "%#{params[:query][:keyword]}%")
# Think it's the above, though could be the following:
# #products = Product.where('lower(title) LIKE "%?%"', params[:query][:keyword])
If you have a read on SQL's LIKE operator, the % operates something like a wildcard. Without these, you're searching for an exact match, rather than a phrase contained within the title. Docs are here.
Give that a shot and let me know how you get on.
First of all you are checking params[:query] twice(once when calling filter_products and second time in that function)
And their is something wrong with you filter_products function.
When you do #products ||= Product.all you get blank ActiveRecordRelation if query returns empty relation. In other words #products will always be blank if query[:keyword] doesn't match the title.
Try changing your index function to:
def index
#products = Product.where('lower(title) LIKE %?%', params[:query][:keyword].downcase) if params[:query][:keyword].present?
puts #products
#products ||= Product.all
end
If it still returns blank, then try to print #products variable.

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 %>

rails 4 populate dropdown values from database

I have a dropdown in a rails form:
<%= f.select :lists, [["test1", 1], ["test2", 0]] %>
This works fine but how can I make it dynamic. (interacting with model data)
I have a controller with an action containing #list = List.all
How can I populate id and name in my combobox. I've been searching around, but I am unclear about it. Can anyone help>
You can use options_from_collection_for_select.
<% options = options_from_collection_for_select(#list, 'id', 'name') %>
<%= f.select :all_val, options %>
Don't quite have enough reputation to respond to your question in the thread above #learner but there's a good chance that #overflow didn't have #list defined in his controller.
To solve my case I put my equivalent of #list (in this case #restaurants) in my "def new" method since I was using it to help create new items with associated restaurants.
# GET /specials/new
def new
#special = Special.new
#restaurants = Restaurant.all // Look Here
end
Additionally, :all_val in the original response should be the parameter you want to pass in to the database. In my case it was :restaurant_id
This worked for me
# view
<%= form.select(:list_id) do %>
<% #list.each do |l| -%>
<%= content_tag(:option, l.name, value: l.id) %>
<% end %>
<% end %>
and
# controller
#list ||= List.all
`

Rails: Multiple models records list

How can I show recent added #post and #photos in one list? For example:
post - LAlala (10.10.2011)
photos - [] [] [] [] (1.1.2011)
post - Bbbdsfbs (2.12.2010)
post - Lasdasdf2 (2.10.2009)
#posts = Post.limit(20).order('created_at desc')
#photos = Photo.limit(20).order('created_at desc')
#recent_items = (#posts + #photos).sort_by(&:created_at)
<% #recent_items.each do |item| %>
<% if item.class == "Photo" %>
<%= image_tag item.url %>
<% else %>
<%= item.title %>
<% end %>
<% end %>
Alternatively, use group_by to do it like this:
#recent_items = (#posts + #photos).group_by(&:created_at)
<% #recent_items.each do |date, items| %>
Date: <%= date %>
<% items.each do |item| %>
Show information here.
<% end %>
<% end >
I would move the view logic into a helper if I were you for DRYer code.
It is much better to do this is the database.
I just say this: polymorphism + database views.
Create a database view which contains the columns you need from both Post and Photo, including the column "type" containing a the name of the model (you need it for the polymorphism). Call this view for example "list_items". Then create a model called "ListItem". Then you can use this model like any other, paginate it and whatever you need to do.
ListItem.order("created_at > ?", Date.yesterday).page(params[:page])
And don't forget to configure the polymorphic association
However, all this is much easier to accomplish with the listable gem. Check it out!

Resources