Get an array of associations from an array of objects? - ruby-on-rails

I have an array of Pictures. Each picture has_many comments.
If I have an array of pictures #pictures, how can I get all comments with a certain attribute from all pictures in #pictures? Is there a nice Ruby one-liner for the following code?:
#comments = []
#pictures.each do |pic|
pic.comments.each do |comment|
if comment.text == "test"
#comments << comment
end
end
end
Note: I know I can probably get this in one line from a database query, but I figure it would be more efficient to use the data that I already have, rather than re-query the database for ALL pictures, when I only care about a certain subset of pictures that I already have.

#comments =
#pictures
.flat_map(&:comments)
.select{|comment| comment.text == "test"}

map + select should do the trick:
#comments = #pictures.map(&:comments).flatten.select{|c| c.text == "test"}

Related

How to give an order to some values in a multiple Class array in Rails?

I have an Array, composed by multiple Class objects, sorted by shuffle :
#streams = (Product.all + List.all + Post.all).shuffle
In a feed page (like the timeline page on Facebook), the content of the object is displayed with .each, and on each Class, a specific partial is applied :
#streams.each do |stream|
<% if stream.is_a?(Product) %>
<%= render 'product_partial', object: stream %>
<% elsif stream.is_a?(List) %>
<%= render 'list_partial', object: stream %>
<% end %>
<% end %>
Goal : because there is in the app a lot of Products (e.g : 200), and less Posts (100) and even less Lists (10), I want to give an order to each content, with the Class. With this, the Posts & Lists will not be drowned on Products.
In one sentence : for 20 products, show 2 posts, 1 list.
Any ideas ?
Many thanks.
I would use probabilities and a native ruby enumerator:
#streams =
[Product, List, Post].map(&:all).map(&:shuffle)
type =
case rand 23
when 0..19 then 0
when 20..21 then 1
else 2
end
#streams[type].pop # pop one element from the respective array
This has a drawback one type might end before others and you probably will need to explicitly check for this and use still non-exhausted types, but it seems to be better than an explicit 1-per-2-per-20 because it still has a pseudo-random order to some extent.
You could do something like:
#streams = custom_order(Products.all, Lists.all, Posts.all)
def custom_order(products, lists, posts)
products_blocks = products.in_groups_of(20, false)
lists_blocks = lists.in_groups_of(2, false)
posts_blocks = posts.in_groups_of(1, false)
result = []
biggest_array = [products_blocks.length, lists_blocks.length, posts_blocks.length].max
1.upto(biggest_array) do |_|
# here we're pushing the blocks, result will be something like [[product, product .. product] [list, list] [posts]]
result << products_blocks.shift
result << lists_blocks.shift
result << posts_blocks.shift
# is ok if one of the blocks is nil, we'll use compact later
end
# result => [[product product product] [list list] [post] [product product] [list] nil]
# compact to remove the nils
# result => [[product product product] [list list] [post] [product product] [list]]
# after flatten
# result => [product product product list list post product product list]
result.compact.flatten
end
EDIT: changed compact and flatten

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 4 fields_for number of repetitions

I would like to display a form with four nested fieldsets for associated objects. The only way I've found is to override the initialize method and define four associations:
RUBY
def initialize(attributes = {})
super
4.times { items << Item.new }
end
and then display nested fields normally:
HAML
= f.fields_for :items do |item|
= render 'item_fields', f: item
This is not working when I try to edit objects that already exist and have fewer number of associated items.
Any help will be appreciated.
MORE INFO:
Order has_many items
OrderSet has_many orders
Orders are added through the cocoon gem (there is at least one order in each set)
There should always be four items for each order. But when there are less items I don't want to save empty records, instead I would like to just display remaining items as empty.
The initialize is not the place as it is executed every time a new Order instance is created, this means: also when retrieving an existing order from the database.
Imho the view is also not the optimal place.
I would solve this in the controller:
def new
#order = Order.new
4.times { #order.items.build }
end
and then you can just leave your model/view as they were originally.
If you always want to show 4 nested items, you can do something similar in the edit action (to fill up to 4)
def edit
#order = Order.find(params[:id])
(#order.items.length...4).each { #order.items.build }
end
In my personal opinion this is cleaner then doing it in the view.
[EDIT: apparently it is a double nested form]
So, in your comment you clarified that it is a double-nested form, in that case, I would use the :wrap_object option as follows (it gets a bit hard to write a decent example here, without you giving more details, so I keep it short and hope it is clear). I am guessing you have a form for "something", with a link_to_add_association for :orders, and that order needs to have several (4) items, so you could do something like:
= link_to_add_association('add order', f, :orders,
:wrap_object => Proc.new { |order| 4.times { order.items.build}; order })
Before your f.fields_for in your view, or even in your controller, you can check the length of .items() and create new objects as required:
(o.items.length...4).each { f.object.items << Item.new}
= f.fields_for :items do |item|
= render 'item_fields', f: item

flltering array depending upon the models or table names in rails 3.2

i have a resultset(array) returned from a solr search which contains different rows from different tables(models).NOW suppose my models are image,video(there are many more) etc and my resultset contains record from every table,so how can i filter in such a way that after filtering i should have different objects collected in different instance variables...
for example
search = Sunspot.search Video ,Image do
keywords(params["search"])
#fulltext params[:search]
order_by(:created_at, :desc)
end
####this doesnt works and fails if no video/image object is present
#videos << search.results.first(10).select{|x| x.class.name=="Video" } if search.results.include?(Video)
#images << search.results.first(10).select{|x| x.class.name=="Image" } if search.results.include?(Image)
so is it possible to do that i want,,moreover is there a way to check that my array includes an object of model.
example----
search.include?(Video)
or
search.any.contains?(Video)
right now i am using kind_of?/is_a? in my view and iterate through search result,that i dont want ,hence looking for a better solution to display the searched results
I would try something like this:
search = Sunspot.search(Video, Image) do
keywords(params[:search])
# fulltext params[:search]
order_by(:created_at, :desc)
end
results = search.results[10].presence || []
#videos = results.select? { |result| result.is_a?(Video) }
#images = results.select? { |result| result.is_a?(Image) }
You could group the result by their class, and work with the resultant hash.
search = Sunspot.search(Video, Image) do
keywords(params[:search])
# fulltext params[:search]
order_by(:created_at, :desc)
end
#results = search.results[10].group_by(&:class)
#videos = #results[Video]
#images = #results[Image]
But there's little need for the #videos and #images instance variables; In your views you can render the value at each key (or an empty collection if the value is nil) <%= render #results.fetch(Video, []) %> and the _video.html.erb partial will be rendered for each one of the video objects.

Rails Given an array of objects from a db, is there a way to get an item in the array w/o having to rehit the db?

Given:
#votes (user_id, option_id)
If do: #project.votes I get all the votes for that project.
If I then want to see what the current user voted for I have to do:
Votes.where(:user_id => current_user.id).first
This is a record that's already in the #project votes query. Is there a way I can find the record in that first query w/o having to rehit the db?
Thanks
You can just use the regular ruby Enumerable#select method:
#votes = project.votes.all
# ... other code ...
current_user_votes = #votes.select{ |v| v.user_id == current_user.id }
This will return an array of all the user's votes for the project. If the user is only allowed one vote, and you want a single value, not an array, just use .first like so:
#votes = project.votes.all
# ... other code ...
current_user_vote = #votes.select{ |v| v.user_id == current_user.id }.first

Resources