Creating "feeds" from multiple, different Rails models - ruby-on-rails

I'm working on an application that has a few different models (tickets, posts, reports, etc..). The data is different in each model and I want to create a "feed" from all those models that displays the 10 most recent entries across the board (a mix of all the data).
What is the best way to go about this? Should I create a new Feed model and write to that table when a user is assigned a ticket or a new report is posted? We've also been looking at STI to build a table of model references or just creating a class method that aggregates the data. Not sure which method is the most efficient...

You can do it one of two ways depending on efficiency requirements.
The less efficient method is to retrieve 10 * N items and sort and reduce as required:
# Fetch 10 most recent items from each type of object, sort by
# created_at, then pick top 10 of those.
#items = [ Ticket, Post, Report ].inject([ ]) do |a, with_class|
a + with_class.find(:all, :limit => 10, :order => 'created_at DESC')
end.sort_by(&:created_at).reverse[0, 10]
Another method is to create an index table that's got a polymorphic association with the various records. If you're only concerned with showing 10 at a time you can aggressively prune this using some kind of rake task to limit it to 10 per user, or whatever scope is required.

Create an Item model that includes the attributes "table_name" and "item_id". Then create a partial for each data type. After you save, let's say, a ticket, create an Item instance:
i = Item.create(:table_name => 'tickets', :item_id => #ticket.id)
In your items_controller:
def index
#items = Item.find(:all, :order => 'created_on DESC')
end
In views/items/index.erb:
<% #items.each do |item| %>
<%= render :partial => item.table_name, :locals => {:item => item} %><br />
<% end %>

Related

rails order by association through another association?

As a simple example, let's say a bookstore has books which have one author. The books has many sales through orders. Authors can have many books.
I am looking for a way to list the authors ordered by sales. Since the sales are associated with books, not authors, how can I accomplish this?
I would guess something like:
Author.order("sales.count").joins(:orders => :sales)
but that returns a column can't be found error.
I have been able to connect them by defining it in the Author model. The following displays the correct count for sales, but it does ping the database for each and every author... bad. I'd much rather eager load them, but I can't seem to get it to work properly since it will not list any authors who happen to have 0 sales if I remove the self.id and assign the join to #authors.
class Author < ActiveRecord::Base
def sales_count
Author.where(id: self.id).joins(:orders => :sales).count
end
end
And more specifically, how can I order them by the count result so I can list the most popular authors first?
Firstly, let's have all associations available on the Author class itself to keep the query code simple.
class Author < AR::Base
has_many :books
has_many :orders, :through => :books
has_many :sales, :through => :orders
end
The simplest approach would be for you to use group with count, which gets you a hash in the form {author-id: count}:
author_counts = Author.joins(:sales).group("authors.id").count
=> {1 => 3, 2 => 5, ... }
You can now sort your authors and lookup the count using the author_counts hash (authors with no sales will return nil):
<% Author.all.sort_by{|a| author_counts[a.id] || 0}.reverse.each do |author| %>
<%= author.name %>: <%= author_counts[author.id] || 0 %>
<% end %>
UPDATE
An alternative approach would be to use the ar_outer_joins gem that allows you get around the limitations of using includes to generate a LEFT JOIN:
authors = Author.outer_joins(:sales).
group(Author.column_names.map{|c| "authors.#{c}").
select("authors.*, COUNT(sales.id) as sales_count").
order("COUNT(sales.id) DESC")
Now your view can just look like this:
<% authors.each do |author| %>
<%= author.name %>: <%= author.sales_count %>
<% end %>
This example demonstrates how useful a LEFT JOIN can be where you can't (or specifically don't want to) eager load the other associations. I have no idea why outer_joins isn't included in ActiveRecord by default.

how to append database records in to a variable in ruby on rails

I fetched all users from the database based on city name.
Here is my code:
#othertask = User.find(:all, :conditions => { :city => params[:city]})
#othertask.each do |o|
#other_tasks = Micropost.where(:user_id => o.id).all
end
My problem is when loop gets completed, #other_task holds only last record value.
Is it possible to append all ids record in one variable?
You should be using a join for something like this, rather than looping and making N additional queries, one for each user. As you now have it, your code is first getting all users with a given city attribute value, then for each user you are again querying the DB to get a micropost (Micropost.where(:user_id => o.id)). That is extremely inefficient.
You are searching for all microposts which have a user whose city is params[:city], correct? Then there is no need to first find all users, instead query the microposts table directly:
#posts = Micropost.joins(:user).where('users.city' => params[:city])
This will find you all posts whose user has a city attribute which equals params[:city].
p.s. I would strongly recommend reading the Ruby on Rails guide on ActiveRecord associations for more details on how to use associations effectively.
you can do it by following way
#othertask = User.find(:all, :conditions => { :city => params[:city]})
#other_tasks = Array.new
#othertask.each do |o|
#other_tasks << Micropost.where(:user_id => o.id).all
end
Here is the updated code:
#othertask = User.find_all_by_city(params[:city])
#other_tasks = Array.new
#othertask.each do |o|
#other_tasks << Micropost.find_all_by_user_id(o.id)
end
You are only getting the last record because of using '=' operator, instead you need to use '<<' operator in ruby which will append the incoming records in to the array specified.
:)
Try:
User model:
has_many :microposts
Micropost model:
belongs_to :user
Query
#Microposts = Micropost.joins(:user).where('users.city' => params[:city])

Rails 3 - is possible to make IFs in a database query

I have a profile card of user that have registration in my forum.
Person.update_all({:name => params[:person][:name],
:sex => params[:person][:sex],
:age => params[:person][:age],
:avatar => params[:person][:avatar].original_filename,
:city => params[:person][:city]},
{:id => params[:id]})
This is query for updating data in database. But here is a small problem - this will work only in a situation, if I the user send through form avatar (image). If not send avatar - that means the user already have uploaded avatar and the form send only name, sex, age and city. So in this case I'll get error in line :avatar => params[:person][:avatar].original_filename, -- and I would like to ask you for, if exist some elegant way, how to treat this moment.
I thought something like this:
if params[:person][:avatar]
avatar = ':avatar => params[:person][:avatar].original_filename,'
end
Person.update_all({:name => params[:person][:name],
:sex => params[:person][:sex],
:age => params[:person][:age],
avatar
:city => params[:person][:city]},
{:id => params[:id]})
But unfortunately, this doesn't work... How you're solving similar situation?
Thank you.
Well, it seems, like your params[:person] keys are similar to your model fields. So why don't you just pass params[:person] to update_all?
Alternatively, you could create a hash person, initialize it the way you want and then pass it to update_all
person = { :name => params[:person][:name] ,
...
if params[:person][:avatar]
person[:avatar] = params[:person][:avatar].original_filename
end
Person.update(params[:id], person)
I've changed update_all to update, because update_all is used to update all the records (that match the condition), while update find's the record by it's ID.
But again, it's a bad practice and you have to type a lot of unnecessary code.
One more thing. update_all makes a direct DB call, which doesn't involve validations, callbacks etc.
So, if you don't have some special reason for this, you'd better do something like this:
#person = Person.find params[:id]
#person.update_attributes params[:person]
I really think, you should check this book out
Updated once again :)
You see, such things belong to your models, not controllers. You could define a setter in the model:
def avatar=(value)
write_attribute(:avatar, value.original_filename)
end

In Rails - How to have one query that has multiple queries?

In have 3 models here:
projects
threads (project_id)
thread_participations (thread_id, read boolean)
Right now I have a list of the user's projects, and the list shows how many threads are unread per project. The huge problem here is that if the user has several projects (which all users do) it causes the DB to get hit with several queries, one per project.
I would like to use Rails to build a query, that with one DB hit, returns an unread count for each of the user's project.
Here's what I use today in the view:
<% #projects.each_with_index do |project, i| %>
<%=project %>: <%= Thread.unread(current_user,project).count %>
<% end %>
And in the thread Model:
scope :unread, lambda { |user,project|
includes(:project,:thread_participations).where(:project_id => project.id, :thread_participations => {:read => false, :user_id => user.id})
}
Any suggestions on how to do this? Also which model should this live in? Maybe the user's model since it is not project or thread specific?
Thanks
There are a couple of ways to structure this query, but here is one.
You can perform this in a single query and then loop over the results. I would first create a scope on thread participations for unread for a certain user. Then use the scope and include all threads and projects, group by the project id (so that you are getting unread threads for that project) and then count the number of unread threads by counting threads.id:
class ThreadParticipations
scope :unread, lambda{ |user| where(user_id: user.id, read: false) }
end
ThreadParticipations
.unread(current_user)
.includes(:thread => :project)
.group('projects.id')
.count('threads.id')
=> { 10 => 15, 11 => 10 }
# project(10) has 15 unread threads and project(11) has 10 unread threads

Collect many 'counts' in one query?

I need to do the following:
<% for customer in #customers do %>
<%= customer.orders.count %>
<% end %>
This strains the server, creating n queries, where n = number of customers.
How can I load these counts along with my customers in one query? Thanks.
You could use an eager join:
#customers = Customers.paginate :page => 1, :per_page => 20, :include => [:orders]
By specifying the :include parameter to the join, the orders will be preloaded, preventing the n+1 problem. You can then use customer.orders.length.
If loading all those orders is too memory-intensive, then you should explore counter_cache. This is designed to keep a count of a model on an associated model:
class Order
belongs_to :customer, :counter_cache => true
end
This will increment and decrement a orders_count field on the owning customer record when orders are added or removed from the associaition.
If you don't want to use the counter_cache, you'll need custom finder SQL which joins the orders table and groups on orders.customer_id, and then selects the count as an extra field. This will not perform nearly as well as the counter cache, though.

Resources