Can I query the current scope(s)? - ruby-on-rails

My collections model has a scope like this
scope :got, -> { where(status: 'Got') }
I have a link to an index as follows
<%= user_collections_path(#user, got: true) %>
Thanks to the has_scope gem that creates an index of the user's collections where the status: is 'Got'. the path is
users/user1/collections?got=true
In that index view I want to be able to write something like
<% if status: 'Got' %>
You have these ones
<% end %>
But no matter how I write it I can't seem to query the scope that was passed in the link. Is there a standard way of doing this?

You can do as following:
<% if params[:got].to_s == 'true' %>
You have these ones
<% end %>
But this forces you to use true as value for params[:got]. Maybe this is better:
<% if params[:got].present? %>
You have these ones
<% end %>
But this will work with params like:
users/user1/collections?got=true,
users/user1/collections?got=false,
users/user1/collections?got=NOPE,
etc.
Actually, the has_scope gem provides a method current_scopes that returns a hash (key = scope, value = value given to the scope) in the corresponding views. You should be able to do like this:
<% if current_scopes[:got].present? %>
You have these ones
<% end %>

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.

Get model type from each loop

I'm combining two models in my controller, and trying to print them in a similar fashion, but I'd like to do an if statement in an each loop to distinguish one model from the other. My models are comments and likes.
In the controller:
#items = (#user.likes + #user.comments).sort{|a,b| a.created_at <=> b.created_at }
In my view:
<%= #items.each do |item| %>
<% item.name %>
<% end %>
I need an if statement to say IF comment or IF like in the each loop. I've been drawing a blank on the situation.
Assuming you have model Like and Comment
<%= #items.each do |item| %>
<% if item.instance_of?(Like) %>
Something for likes
<% elsif item.instance_of?(Comment) %>
Something for comments
<% end %>
<% end %>
You could do item.class.name as Nithin mentioned, and it'll work fine. However, the more idiomatic way is to use instance_of?. So it'd look like this:
if item.instance_of?(Post)
# do something
elsif item.instance_of?(User)
# do something else
end
Note you're not passing in 'Post' or 'User' as strings - you're passing in the Ruby class constants themselves.
On this topic, it's also worth knowing about Ruby's is_a? and kind_of? methods which work similar to instance_of? but return true if the instance you're testing is an instance of a subclass of the parameter you pass in (more info at Ruby: kind_of? vs. instance_of? vs. is_a?).
What you need to do is to check for the class of the Item of interest.
Basically, each object belongs to a class, and the class name of a comment will be a Comment while that of a like will be a Like.
So, in the loop, check for the class name as:
<%= #items.each do |item| %>
<% if item.class == Comment %>
...comments here
<% elsif item.class == Like %>
...likes here
<% end %>
<% end %>

Test ownership in a helper

I have this block in my views:
<% video.members.each do |p| %>
<% if p.id == current_user.id %>
<%= "paid" %>
<% end %>
<% end %>
Basically I'm trying to work out if a member has paid for a video based on whether the id's match.
Maybe this a really bad way of doing it, which case I'd be happy to try and different method.
Assuming it is an ok way of checking this, how could I write a similar statement but as a helper method? I've tried, but it seems you can't write the same logic in helpers as the block just spits out the full array and not the id, meaning it doesn't work.
You should do this instead:
<% if video.members.exists?(id: current_user.id) %>
<%= 'Paid' %>
<% end %>
This will generate a single query to test if the video has been paid by the current_user ;-)
In a helper:
# application_helper.rb
def display_paid_or_not(video)
return '' if video.blank? # similar to .nil?
video.members.exists?(id: current_user.id) ? 'Paid' : ''
end
# in view
<%= display_paid_or_not(video) %>
Hope this helps!

Get list of unique column results from database

I've stumbled across this answer and it has helped me to generate a list of unique values exactly as I wanted, however, I don't want all of the results. Is there any way to filter the results within the select or another way to accomplish this?
I was thinking of something along the lines of this:
#results = MyModel.select("DISTINCT(columnForDistinction)", :myBoolean => false)
or
#results = MyModel.select("DISTINCT(columnForDistinction)", :someString => stringImLookingFor)
Currently, I'm not able to filter the results on the query, so I am iterating over the returned array and only listing the results that have that boolean set to false like so:
<% #results.each do |result| %>
<% if !result.myBoolean %>
#do stuff here
<% end %>
<% end %>
and
<% #results.each do |result| %>
<% if result.someString == stringImLookingFor %>
#do stuff here
<% end %>
<% end %>
Is there a better way to be doing this?
ActiveRecord query methods are chainable. You can call multiple and it will build them all into a query when you use the result. For conditions, you'll use where. Try something like:
#results = MyModel.select("DISTINCT(columnForDistinction)").where(:myBoolean => false)

Rails finding a value in a table

Is it possible to call the include? function on a whole table, like this?
<% #user.games.each do |g|
##latestround = g.rounds.order('created_at DESC').first
%>
<% if ##latestround.submittedpictures.isFinalPicture.include?(true) %>
<p>FinalPicture has been played!</p>
<% end %>
<% end %>
The problem i'm getting is that It only works when I put a block on submittedpictures and then loop through each record of this table. However I want to look through the whole table in one go and see if the column 'isFinalPicture' includes a value with 'false'.
Any ideas?
The following snippet works but its not the way i want it (I would get more lines if the round happens to have more 'true' FinalPictures)
<% ##latestround.submittedpictures.each do |s| %>
<% if s.isFinalPicture == true %>
<p>Final Picture has been played!</p>
<% end %>
<% end %>
You could make a scope for it like
class SubmitedPricture << ActiveRecord::Base
scope :final_pictures, where('isFinalPricture = ?', true)
end
then you could see if there is any with only one query
latestround.submittedpictures.final_pictures.any?
Also you should follow the conventions of Rails in naming your Models and everything else. Like submittedpictures should be submitted_pictures

Resources