Rails Get Multiple by ID - ruby-on-rails

In Rails, I have a Product model. Sometimes I need to get multiple products at the same time (but the list is completely dynamic, so it can't be done on the Rails side).
So, let's say for this call I need to get products 1, 3, 9, 24 in one call. Is this possible? If so, do I need a custom route for this and what do I put in my controller?
i.e. does something like this work? /products/1,3,9,24

I don't think you should need to change the routes at all. You should just have to parse them in your controller/model.
def show
#products = Product.find params[:id].split(',')
end
If you then send a request to http://localhost/products/1,3,9,24, #products should return 4 records.

I would consider this a request to index with a limited scope, kind of like a search, so I would do:
class ProductsController < ApplicationController
def index
#products = params[:product_ids] ? Product.find(params[:product_ids]) : Product.all
end
end
and then link to this with a url array:
<%= link_to 'Products', products_path(:product_ids => [1, 2, 3]) %>
this creates the standard non-indexed url array that looks kind of like
product_ids[]=1&product_ids[]=2 ...
Hope that helps.

Product.where(:id => params[:ids].split(','))

Related

Ruby on Rails 5: Find index of post_id and display in view (post # of n)

I have a resource :posts, which I show one at a time in show.html.erb
Suppose I have ten posts, each with an :id going from 1-10. If I delete post #2, then my posts will be 1,3,4,5,6,7,8,9,10. If I create ten posts and delete them all, then the next post :id would be [1,3..10,21] but I would only have 11 posts.
I want to show the post number that's in the application and put it in the view against a total number of posts. So if you were looking at post #3, it might have an :id of 3, but it is post #2 in the database.
Here's what I tried so far:
posts_controller.rb
def show
...
#post = Post.friendly.find(params[:id])
#total_posts = Post.all.count.to_i
#posts_array = Post.pluck(:id).to_a
...
end
views/posts/show.html.erb
<%= #post.id %> of <%= #total_posts %> /
models/post.rb
def next
Post.where("id > ?", id).order(id: :asc).limit(1).first
end
def prev
Post.where("id < ?", id).order(id: :desc).limit(1).first
end
However, showing the :id of a resource is a security issue so I don't know how to do it better.
How can I make it so the show.html.erb view only shows the current index order of the total amount of resources as compared to the post_id?
An efficient way to do this could be
# app/controllers/posts_controller.rb
def show
#post = Post.friendly.find(params[:id])
#total_posts = Post.count
#post_index = Post.where("id <= ?", #post.id).count
end
# app/views/posts/show.html.erb
. . .
<%= #post_index %> of <%= #total_posts %>
. . .
You should avoid loading all posts (or even their id) if you can. This will become more and more expensive as the number of posts grows and will eventually become a bad bottleneck for performance.
If you're trying to find the 'array index' of a record (so to speak) you can do this:
Agency.order(id: :asc).offset(params[:index]).limit(1)
You don't really want to do any other way because then it will load EVERY record into rails which will be very slow. It's better to ask the database for only a single record (which is what 'offset' does). Just replace params[:index] with whatever the name of the params is, whether its params[:id], etc.
I did just want to address one thing you said:
However, showing the :id of a resource is a security issue so I don't know how to do it better
That's not a security issue. The app should be designed in a way where the ID of a resource is not special or "secret." If you have an ID of a record, your controller should work such that it "authorizes" certain actions and won't let you do something you're not supposed to (like a user deleting a post).
If you REALLY need to do this, then just hide the ID and use a slug instead, like example.com/this-is-a-post-slug. This can be done quite easily
Edit To answer your specific question...
ids = Agency.order(id: :asc).pluck(:id)
#post_index = ids.find_index(#post.id)
#next_post = ids[#post_index + 1]
#prev_post = ids[#post_index - 1]
You can now use #post_index in your view.
Note: #prev_post and #next_post will be nil when the page doesn't exist (i.e. the "next post" when you're on the last page), so you will need to check that.
Just try it:
def show
...
#post = Post.friendly.find(params[:id])
#total_posts = Post.count # this will return integer type data
#posts_array = Post.pluck(:id) # you don't need to_a as .pluck returns array
...
For the next part you could write:
def next
self.class.where("id > ?", id).limit(1).first # this use of id is secured.
end
def prev
self.class.where("id < ?", id).order(id: :desc).limit(1).first
end

Ruby on Rails: custom rendering in controller

So basically I have this for my index page:
def index
#articles = Article.paginate(:per_page => 15, :page => params[:page])
end
I implemented soft deletion, which basically works like this: there is an additional boolean column in db that is called is_active and is true by default. Deleting is rewritten to just change that to false instead of destruction, and made a page to view soft-deleted entries.
The issue: current workaround I found for paginate is to simply add
<% if article.is_active %>
in my index.html.erb. The flaw is: when I delete something it still considered there by paginate, so instead of say 15 entries I will see 14. Even worse, on undeleting page it shows same amount of blank pages, and deleted entries are on their would-be-appropriate pages (so for example first entry may end up being on page 14 instead of 1). It is not critical flaw, but I'd like to know if I can fix it without rewriting too much.
Maybe I can change something in controller so it doesn't send any entries that have true or false in that field depending on what I want to output?
Can't you just filter by this field?
#articles = Article.where(is_active: true).paginate(per_page: 15, page: params[:page])
You can use a scope or a where clause to get only active articles:
def index
#articles = Article.where("is_active = ?", true).paginate(:per_page => 15, :page => params[:page])
end

How can I change the number of items that end up in a list

I'm hacking away at a rails project and I wanted to modify the number of items that end up on a particular page. The page gets populated via an array of items.
For the life of me I can't figure out how to make it show only 2 instead of 4 items.
In the haml file there is this section:
%ul.story-list
- #stories.each do |story|
%li
- unless story.image.blank?
.img-container{ class: ((story.video.blank?)? "": "video-container") }
= image_tag(story.image_url, alt: story.name, class: ((story.video.blank?)? "": "js-has-video"), :video => story.video)
.story-data
%h4= story.name
%h5.location= story.location
%p.quote= story.story
- if story.get_connected?
= link_to 'Get Connected', connect_path
- elsif story.gather_supplies?
= link_to 'Gather Supplies', supplies_path
- elsif story.make_a_plan?
= link_to 'Make a plan', plan_path
The page shows up (on the server) with four story items, I want it to only show two. I was expecting to open the haml file and just delete some lines (or comment them out). I'm so confused.
So, I suspect the number of stories comes from a controller or something like that. ..but maybe it is coming from the placeholder data on the server?
In case you are inspired to help me, all the code is here
https://github.com/city72/city-72
The exact page I'm trying to modify is this one, I want it to only have two stories:
http://collier72.herokuapp.com/stories
Weirdly, in my local environment I can't edit the stories at all. That's what makes me thing the number of items comes from the data.
The stories controller is this tiny little file that doesn't specify the number of stories:
class StoriesController < ApplicationController
after_filter :static_content
def index
all_stories = EmergencyStory.order("index,id ASC").all
#selected_story = all_stories.select {|s| s.selected}.first
#stories = all_stories.collect.select {|s| !s.selected}
end
end
Open up this file:
https://github.com/city72/city-72/blob/master/app/controllers/stories_controller.rb#L8
Change that line from this:
#stories = all_stories.collect.select {|s| !s.selected}
to this:
#stories = all_stories.collect.select{|s| !s.selected}.slice(0,2)
From what I can tell, the fact it is returning 4 isn't intentional, it's just what is in the database. The slice(0,2) will return the first two items.
First, you have 3 stories that you are looking for, not 2. You have your #selected_story and then the remaining #stories. Second, you are retrieving ALL of the stories which will not scale when you get many stories in the database, so rendering this page will slow down over time. So you need to limit the number of records being returned by the database.
Get the selected story.
Then get the two next stories.
class StoriesController < ApplicationController
after_filter :static_content
def index
#selected_story = EmergencyStory.where(selected: true).first
#stories = EmergencyStory.where(selected: false) # don't get selected
.limit(2) # limit records returned
.order("index,id ASC")
.all
end
end
If you were to further refine this you should put those two queries into methods into EmergencyStory.
class StoriesController < ApplicationController
after_filter :static_content
def index
#selected_story = EmergencyStory.selected_story
#stories = EmergencyStory.recent_stories
end
end
class EmergencyStory < ActiveRecord::Base
def self.selected_story
where(selected: true).first
end
def self.recent_stories
where(selected: false).limit(2).order('index,id ASC').all
end
end

How can this be re-written as an ActiveRecord query? (Rails)

I have the following method in a controller:
# GET /units/1
def show
#unit = Unit.find(params[:id]
#product_instances = Array.new
current_user.product_instances.each do |product_instance|
if product_instance.product.unit == #unit
#product_instances.push(product_instance)
end
end
... #rest of method
end
As can be seen, I have four tables/models: User, Product, ProductInstance, and Unit. A User has many ProductInstances. Each ProductInstance maps to a Product. A Unit has many Products.
I would like to fetch only the User's ProductInstances that are linked to a Product in the current Unit. The current code does it, but how can I re-write it better? I'd like to get rid of the for-each loop and if statement and replace it with chained ActiveRecord queries, if possible.
I tried something like below but it didn't work:
#product_instances = current_user.product_instances.where(:product.unit => #unit)
Seems you cannot do :product.unit.
I think you can try this
current_user.product_instances.joins(:product).where("products.unit_id = ?",#unit.id)
or with hashes
current_user.product_instances.joins(:product).where(:products => {:unit_id => #unit.id})

Update on record would update all other records in model, global ordering in rails 3.1

I would like to update all records in a rails (3.1) model when i update an attribute on a single record.
Like self.update_attribute(:global_order => 1) then before or after save a would like to update all other records to update thier global_order (1, 2, 3, 4).
Right now with on after_save callback I get caught in a recursive loop, is skip callbacks the way to go? I would like the app to throw exceptions if anything seems strange in global_order.
Or are there any 3.1 gems that would solve my issue.
after_save :set_global_order
def set_global_order
#products = self.class.all(:order => :global_order)
#products.sort! {|a,b| a.global_order <=> b.global_order}
#products.reverse!
#products.each_with_index do |p, index|
p.update_attributes!({:global_order => index + 1})
end
end
Not sure if there's a gem, but you could definitely refactor this with the following considerations:
No need to pollute the object with an instance variable when a local one will do
The first three lines are sorting the same set, why not do that once?
...
def set_global_order
products = self.class.order('global_order DESC')
products.each_with_index do |p, index|
p.update_column(:global_order, index + 1)
end
end

Resources