Display different indices depending on the scope - ruby-on-rails

I have an Orders index page with two links, whose indices I want to filter by the status of the order:
<%= link_to "Current Orders", orders_path(:by_status => "processing") %>
...
<%= link_to "Past Orders", orders_path(:by_status => "completed") %>
My controller looks like:
class OrdersController < ApplicationController
has_scope :by_status
def index
case params[:status]
when "completed"
#past_orders = Order.where(status: "completed")
when "processing"
#current_orders = Order.where(status: "processing")
end
end
end
I'm sure def index is the main problem. But I also can't figure out how to display that in the view page. I have:
<% #past_orders.each do |order| %>
I would appreciate the help.

to solve your problem you can split the render based of your condition from index.html.erb
in index.html.erb create condition if #post_orders has contents then render past_orders else render current_order
<% if #post_orders %>
<%= render 'past_order.html.erb' %>
<% else %>
<%= render 'current_order.html.erb' %>
<% end %>
then you create two partial file with name _past_order.html.erb and _current_order.html.erb put in same folder with index.html.erb

If you want the view to look the same for both scopes then you should handle it at the controller. It looks like you're using the has_scope gem, so this should work:
class OrdersController < ApplicationController
has_scope :by_status, only: :index
def index
#orders = apply_scopes(Order)
end
end
You need to have a matching scope method on the Order model
class Order < ApplicationRecord
scope :by_status, ->(status) { where status: status }
end
In your view orders/index.html.erb you would handle the collection exactly the same way, using #orders for both current and past orders
<% #orders.each do |order| %>
If you ever need to display a component of the view differently depending on the order status just add an if statement
<% if order.status == "completed" >
<p>Something<p>
<% else >
<p>Something else<p>
<% end >

Related

How to render search results only when the user press search

Does anyone know how to only display search results once you have clicked the search button?
At the moment, my page is currently displaying everything from my #flights.each. But I only want this information to become visible once they have clicked search :)
my index.html.erb
<h1>Flights#index</h1>
<p>Find me in app/views/flights/index.html.erb</p>
From Airport:
<%= form_for(flights_index_path, method: :get) do %>
<%= text_field_tag :from_airport_id, params[:from_airport_id] %>
<%= submit_tag 'Search' %>
<% end %>
<% #flights.each do |f| %>
<br>
<br>
Flight from <%= f.from_airport_id %>
Arriving at <%= f.to_airport_id %>
<% end %>
my controller
class FlightsController < ApplicationController
def index
if params[:from_airport_id]
#flights = Flight.where('from_airport_id LIKE ?', "%#{params[:from_airport_id]}%")
else
#flights = Flight.all
end
end
private
def flights_path
params.require(:flight).permit(:flight, :from_airport_id)
end
end
I believe it's because you have #flights = Flight.all in the else condition. When there is no query(such as after hitting the submit button and passing over the params) it will default to show all the flights. I'd take this line out and only have
def index
if params[:from_airport_id]
#flights = Flight.where('from_airport_id LIKE ?', "%#{params[:from_airport_id]}%")
else
#flights = []
end
end
Or you can look to have an AJAX request from your flights search and render the form that way.

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

Ruby Parameter Passing

I currently have the following snippets of code:
<tbody>
<% #requests.each do |request| %>
<% #user = User.where(id: request[:student_id_id]).first %>
<tr>
<td><%= #user.last_name.titleize %></td>
<td><%= #user.first_name.titleize %></td>
<td><%= #user.preferred_name.titleize %></td>
<td><%= render :partial => 'documents/resumelink' %></td>
<td><%= mail_to #user.email.downcase %></td>
<% if request.allowed_companies.nil? || request.allowed_companies.empty? %>
<td> <a class="no_selected_companies_button">No Restrictions</a></td>
<% else %>
<% #allowed_request = AllowedRequest.where(professor_id_id: #current_user[:id], student_id_id: #user[:id]).first %>
<td><%= link_to("View Companies", allowed_request_path(#allowed_request) , class: "selected_companies_button") %></td>
<% end %>
</tr>
<% end %>
</tbody>
in a partial view. The documents/_resumelink partial looks like this:
<% if (#document != nil) && (#document.resume_file_name != nil) %>
<%= link_to 'View Resume', #document.resume.url %>
<% else %>
<%= link_to 'No Resume available', '#' %>
<% end %>
In the documents_controller I have it so it will before_action set_document (with no exceptions). That definition looks like:
def set_document(user=#current_user)
#document = Document.find_by owner_user_id_id: user[:id]
end
So, what I want to be able to do is pass the #user variable to my set_document function in the render: partial => 'documents/resumelink' line in the first view and have it display a link to the resume (if there is one).
Any ideas?
I think your issue is more systemic than specific; and as such I'd like to give some ideas
Controller
When you use before_action, it runs before your Rails action is run
This means when you send a request to Rails, it loads the before_action filter, and then the relevant action that you've requested. Partials do not form part of this process:
This means if you want to make various variables available to different parts of your application, you need to appreciate that it works as follows:
Request -> Router -> Controller#Action -> View -> Partial
To make use of the appropriate data, you need to ensure it's defined at the correctpart of the above process. The problem you have is you're not doing that -- how can you make the #user variable available to a method before the #user variable has been defined?
This is not a specific issue for you now; but will be something you will need to keep in mind for next time.
Partial
If you're looking to include data in a partial, you will need to use the locals argument for the render method:
<%= render partial: "documents/resumelink", locals: { your_local_var: #value } %>
--
So, what I want to be able to do is pass the #user variable to my set_document function in the render
As per my simple flow chart above, your partial will not invoke the set_document method, as a partial is literally just an extension of your view
You'll need to have the #user variable set in the controller (I set the ActiveRecord association for you) and then pass it as a local to your partial:
#app/models/request.rb
Class Request < ActiveRecord::Base
has_many :users, foreign_key: "student_id_id"
end
#app/models/user.rb
Class User < ActiveRecord::Base
belongs_to :request, foreign_key: "student_id_id"
end
#app/controllers/documents_conroller.rb
Class DocumentsController < ApplicationController
before_action :set_document
def index
#requests = Request.all
end
private
def set_document(user=#current_user)
#document = Document.find_by owner_user_id_id: user[:id]
end
end
This will allow you to call the following:
#app/views/requests/index.html.erb
<% #requests.each do |request| %>
<% user = request.users.first %>
<%= render partial: "documents/resumelink", locals: {user: user} %>
<% end %>
You can do this way
<%= render :partial => 'documents/resumelink', :locals => {user => #user} %>
A variable user will be accessible in the partial.
Another way:
Variable #user is accessible without any changes in your code, because it is instance variable (not local)
If I understand what you're trying to do, I would do it differently.
Assigning variables (especially instance vars) in views is not a good practice, making DB queries from views is also quite bad.
What you can do instead is:
#requests = Request.where(your_conditions).includes(users: :documents)
and then i a view you can do
#requests.each do |request|
request.user.email
render partial: 'documents/resumelink', document: request.user.document
(actually you can do it already but solution proposed above will save you a lot of SQL queries)

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