In a Rails 3.2 app I have a polymorphic ActivtyFeed model.
class ActivityFeed
belongs_to :user
belongs_to :feedable, polymorphic: true
end
I need to aggregate some items in the ActivityFeed index view. For example, instead of rendering separate items for each photo, I want to group photos by a date or event, and display "User uploaded x photos".
My controller looks like this:
#feed_items = #user.activity_feeds.sort_by(&:created_at)
#feed_array = []
photos = #feed_items.find_all{|f|f.feedable_type.eql?("Photo")}
#feed_items.delete_if{|f|f.feedable_type.eql?("Photo")}
#feed_array << #feed_items
grouped_photos = photos.group_by{|p| p.feedable.event_id}
#feed_array << grouped_photos
#feed_array.flatten!
Then in the views I'm using e.g.
if feed_array.class.eql?(Hash)
render grouped photo partial
elsif feed_array.feedable_type.eql?("Post")
render single post partial
etc
I'm having trouble sorting items chronologically, because the array contains a nested hash.
[#<ActivityFeed id: 7, user_id: 2, feedable_type: "Post", feedable_id: 3>, {2=>[#<ActivityFeed id 3, user_id: 4, feedable_type: "Photo", feedable_id: 6>]}]
How can I sort this array?
I've tried #feed_array.sort{|a,b| a.['created_at'] <=> b.['created_at'] } but got comparison of ActivityFeed with Hash failed
Is this the best approach, or is there a better way to go?
you can merge the array and the hash and then sort it
merged.sort {|a,b| a.method <=> b.method }
you only need to tell the sort how to sort the objects
one suggestion, instead of
#user.activity_feeds.sort_by(&:created_at)
do
#user.activity_feeds.order('created_at ASC')
that should be faster, since you already get the activities sorted from the database (if activity_feeds is a relation)
Well you need to do little bit modification to your original design.
What I would suggest is to add event to activity feed and also you need to keep the original photo to the activity feed as well.
Because, by keeping photo as a activity feed you will be able to display a single photo on your activty stream when the photo is not associated with any event. But, if every photo belongs to an event then you can remove the photo from activity feed.
Then every object will be activity feed and can be sorted easily based on created_at.
Also you don't need to extract the photos manually.
Hope this will help!
Related
I want to know if there is an elegant way to merge multiple records from different Models into one new object ?
The case is to build a 'stream' or 'feed' of the app content, like in Facebook.
In detail, each record have multiple and different columns. This is the Models and columns :
Product [name, price]
Post [tagline, image]
Member [name, username, profilepic]
Selection [name, tagline]
The code I tried :
#new_object = #product = Product.find(n) + #post = Product.find(n) + #member = Member.find(n) + #selection = Selection.find(n)
But this is not working because of the differences of fields. I think we have to map into a Hash ?
This is how I will use the final object :
#new_object.each do |stream|
stream.foo
stream.bar
end
So, the goal : take each record and display the content with .each
Luxury : have the oportunity to sort randomly the results.
Many thanks!
You can create a PORO (plain old ruby object) to represent the combined object so something like:
class Stream
attr_accessor :product, :post, :member, :selection
def initialize(attrs)
assign_attributes(attrs)
end
end
Then just initialize and create as many stream objects as you need using Stream.new(attrs). You can create an array of stream objects and loop through them to render or show multiple stream data.
For sorting an array of objects by its attributes you can use
objects.sort_by {|obj| obj.attribute}
I have this code:
Business.all.limit(50).each do |business|
card = {name: business.name, logo: business.logo, category: business.category.name}
feed << card
end
In my models, Business belongs to Category, and Category has many Business
My problem is that this will query the DB 50 times, each time I want to retrieve each business' category name.
I have seen Rails cache effectively by using :include, but all examples I have seen are for child records, for example:
Category.all :include => [:businesses]
but in this case I want to cache the parent's data.
Its the same you can do by using singular model name
Business.includes(:category)
I have the following resource relationship:
Class Course < ActiveRecord::Base
has_many :track_courses
has_many :tracks, through: :track_courses
end
as well as a mirroring relationship inside the Track model. The TrackCourse table which connects these models has these rows:
id: primary key
track_id: represents the track
course_id: represents the course
position: the ordering of the course inside that track
I want to allow admin users to be able to update the courses in each track via ajax. I have a list on the front-end that is being passed to the controller as a hash:
front_end_list = { course_id => position }
which represents the object and its position on the front-end sortable.
I'm also looking up the list of existing courses in that track:
existing_courses = TrackCourse.where("track_id = ?", track_id).all
GOAL: Compare these two lists and syncronize the database entries according to the front-end list. Essentially, if the user inserts Course 15 into position 2 on the webpage, I need to either insert that entry into TrackCourse table (if it doesn't exist) or update its position (if it exists). And vice versa for remove.
What is the best way of doing this? Do ActiveRecord/ActiveRelation provide methods for it? Or do I have to write something myself?
UPDATE: I found a gem called acts_as_list, but it seems to be designed for ActiveRecord tables as opposed to ActiveRelation. It essentially expects position values to be unique, whereas in TrackCourse there can be multiple course with same position (in different tracks).
I figured out a solution. I'll post my code here in case it helps anyone else down the line.
I have this method in my controller that processes the ajax request from the front-end:
def sort
track_id = params[:track_id]
courses_in_list = {}
params[:course].each do |courseid|
position = params[:course].index(courseid)
courses_in_list[courseid.to_i] = position
end
existing_courses_in_track = {}
TrackCourse.where("track_id = ?", track_id).to_a.each do |track_course|
existing_courses_in_track[track_course.course_id] = track_course.position
end
if courses_in_list.length < existing_courses_in_track.length
existing_courses_in_track.each do |courseid, position|
if courses_in_list[courseid].nil?
track_course = TrackCourse.where(track_id: track_id, course_id: courseid).first
track_course.remove_from_list
track_course.destroy!
end
end
else
if existing_courses_in_track.empty?
track_course = TrackCourse.new(track_id: track_id,
course_id: courses_in_list.keys[0])
track_course.insert_at(courses_in_list.values[0])
p "first track!"
else
courses_in_list.each do |courseid, position|
track_exists = false
if !existing_courses_in_track[courseid].nil?
track_course_position = existing_courses_in_track[courseid]
track_exists = true
end
if !track_exists
TrackCourse.new(track_id: track_id, course_id: courseid).insert_at(position)
else
p "else statement"
track_course = TrackCourse.where(track_id: track_id, course_id: courseid).first
track_course.update_attribute(:position, position)
end
end
end
end
render :nothing => true
end
Essentially, I'm building two hashes, one based on the list of front-end items and their position, and one based on the database courses and their position. I then compare them. If the front-end list is shorter, that means the user removed an item, so I iterate through the backend list, find the extra item, and remove it. Then I employ a similar mechanism for adding items to the list and resorting the list. The acts_as_list gem really helps with keeping things in the correct position. However, I did have to limit its scope when I included it in my model to ensure it runs only on relationships (TrackCourses) with a specific track_id.
So I have a CareerEntry model that has the following attributes: name, job_category, company, group, location, year, full_intern, and it represents the job offers that people have received. full_intern is a string that is either "internship" or "full-time", and represents what the type of the job offer is. All CareerEntries will be created by an Admin interface, so it is essentially acting as a standalone model. This is my question: given a bunch of CareerEntry objects, I want to display a table to display on my careers page (which has an action in a PagesController).
I want the table to be sorted according to multiple attributes. I want each year to be its own section in the table, then within each year, I want the internship entries grouped together and the full-time entries grouped together. Then, within these groupings, I want each job_category to be its own section (job_categories comprise of things like 'Investment Banking,' or 'Technology.')
A very good example of what I'm going for is shown under the "2013" tab in this link.
What is the best way to go about achieving this? I know that in the careers action definition of my PagesController, I could have:
class PagesController < ApplicationController
def careers
#careerentries = CareerEntry.order(:year => :desc, :fullintern => :asc, :job_category => :asc)
end
end
But this would simply return all the entries in the order that I want, and would not allow me to place headers and dividers to separate, say, the job_categories.
Is there any easier way of achieving what I'm looking for?
Perhaps you're looking for .group_by?
Group By
From the link you gave, it looks like you want to group your results by year, like this:
#careerentries = CareerEntry.order(year: :desc, fullintern: :asc, job_category: :asc)
#entries_by_year = #careerentries.group_by { |entry| entry.year }
This gives you all the data, ordered to your specs. You can then sort through it, using the group_by method:
#entries_by_year.each do |entry|
entry.name
end
You could then work this into your table
Good reference Group posts by Year - Rails
I have the following objects: Products, Ratings, and Users. I am trying to filter and select a number of Products that the User owns (through a has_many :through relationship with UserProducts) and average a certain column the Ratings table that matches their User ID and the correct Product ID.
So, my function is something along these lines:
def find_rating(criteria)
product = self.products.find(:all, :conditions => ["criteria = ?", criteria])
rating = self.ratings.where("product_id = ?", product).average(:overall)
end
I think that I'm going about this the wrong way, because I'm trying to find a product_id by passing an entire array of data consisting of multiple products. But, I think of using a more traditional loop and that seems convoluted. Can someone point me in the right direction for solving this problem? Thanks!
If product is a single entry, as it appears to be in your code, I would do this:
rating = self.products.find_by_criteria(criteria).ratings.average(:overall)
If it's an array of products, this method may help you: http://apidock.com/rails/ActiveRecord/Batches/ClassMethods/find_each