looping through a rails data object from the controller - sql

I have called a function from a class to find all the items related to a particular ID in a many to many HABTM relationship.
Procedures -> Tasks with a join table: procedures_tasks
I call the information like #example = Procedure.get_tasks(1,1)
I would like to be able to iterate through the data returned so that I can create an instance of each task_id related to the procedure in question
def self.get_tasks(model_id, operating_system_id)
find(:first, :select => 'tasks.id, procedures.id', :conditions => ["model_id = ? AND operating_system_id = ?", model_id, operating_system_id], :include => [:tasks])
end
I tried rendering the data as i normally would and then using .each do |f| in the view layer, but i get:
undefined method `each' for #<Procedure:0x2b879be1db30>
Original Question:
I am creating a rails application to track processes we perform. When a new instance of a process is created I want to automatically create rows for all the tasks that will need to be performed.
tables:
decommissions
models
operating_systems
procedures
tasks
procedures_tasks
host_tasks
procedures -> tasks is many to many through the procedures_tasks join table.
when you start a new decommissioning process you specify a model and OS, the model and OS specify which procedure you follow, each procedure has a list of tasks available in the join table. I am wanting to create a entry in host_tasks for each task relevant to the procedure relevant to the decommission being created.
I've done my head in over this for days, any suggestions?
class Procedure < ActiveRecord::Base
has_and_belongs_to_many :tasks
#has_many :tasks, :through => :procedures_tasks
# has_many :procedures_tasks
belongs_to :model
belongs_to :operating_system
validates_presence_of :name
validates_presence_of :operating_system_id
validates_presence_of :model_id
def self.get_tasks(model_id, operating_system_id)
find(:first, :select => 'tasks.id, procedures.id', :conditions => ["model_id = ? AND operating_system_id = ?", model_id, operating_system_id], :include => [:tasks])
end
end
the get_tasks method will retrieve the tasks associated with the procedure, but I don't know how to manipulate the data pulled from the database in rails, I haven't been able to access the attributes of the returned object through the controller because they haven't been rendered yet?
ideally i would like to be able to format this data so that I only have an array of the task_id's which i can then loop through creating new rows in the appropriate table.

It wasn't looping through because I was using the :first option when finding the data. I changed it to :all which allowed me to .each do |f| etc.
Not the best option, but there will only ever be one option anyway, so it won't cause a problem.

Related

Paginating joined results with calculated columns

We are calculating statistics for our client. Statistics are calculated for each SpecialtyLevel, and each statistic can have a number of error flags (not to be confused with validation errors). Here are the relationships (all the classes below are nested inside multiple modules, which I have omitted here for simplicity):
class SpecialtyLevel < ActiveRecord::Base
has_many :stats,
:class_name =>"Specialties::Aggregate::Stat",
:foreign_key => "specialty_level_id"
.......
end
class Stat < Surveys::Stat
belongs_to :specialty_level
has_many :stat_flags,
:class_name => "Surveys::PhysicianCompensation::Stats::Specialties::Aggregate::StatFlag",
:foreign_key => "stat_id"
......
end
class StatFlag < Surveys::Stats::StatFlag
belongs_to :stat, :class_name => "Surveys::PhysicianCompensation::Stats::Specialties::Aggregate::Stat"
......
end
In the view, we display one row for each SpecialtyLevel, with one column for each Stat and another column indicating whether or not there are any error flags for that SpecialtyLevel. The client wants to be able to sort the table by the number of error flags. To achieve this, I've created a scope in the SpecialtyLevel class:
scope :with_flag_counts,
select("#{self.columns_with_table_name.join(', ')}, count(stat_flags.id) as stat_flags_count").
joins("INNER JOIN #{Specialties::Aggregate::Stat.table_name} stats on stats.specialty_level_id = #{self.table_name}.id
LEFT OUTER JOIN #{Specialties::Aggregate::StatFlag.table_name} stat_flags on stat_flags.stat_id = stats.id"
).
group(self.columns_with_table_name.join(', '))
Now each row returned from the database will have a stat_flags_count field that I can sort by. This works fine, but I run into a problem when I try to paginate using this code:
def always_show_results_count_will_paginate objects, options = {}
if objects.total_entries <= objects.per_page
content_tag(:div, content_tag(:span, "Showing 0-#{objects.total_entries} of #{objects.total_entries}", :class => 'info-text'))
else
sc_will_paginate objects, options = {}
end
end
For some reason, objects.total_entries returns 1. It seems that something in my scope causes Rails to do some really funky stuff with the result set that it gives me.
The question is, is there another method I can use to return the correct value? Or is there a way that I can adjust my scope to prevent this meddling from occurring?
The group statement makes me suspicious. You may want to fire up a debugger and step through the code and see what's actually getting returned.
Is there a special reason you're using a scope and not just an attribute on the SpecialtyLevel model? Couldn't you just add a def on SpecialtyLevel that would function as a "virtual attribute" that just returns the length of the list of StatFlags?
The answer here is to calculate total_entries separately and pass that into the paginate method, for example:
count = SpecialtyLevel.for_participant(#participant).count
#models = SpecialtyLevel.
with_flag_counts.
for_participant(#participant).
paginate(:per_page => 10, :page => page, :total_entries => count)

Using includes with AREL functions

I have the following classes in my application:
class Prompt
has_many :entries
end
class Entry
belongs_to :prompt
belongs_to :user
def self.approved
where("is_approved")
end
end
class User
has_many :entries
end
And I want to display a table of all "approved" entries for a given prompt and the users that they belong_to. To generate this list I do the following query:
prompt = Prompt.find(prompt_id, :include => {:entries => :user})
But when I run the following loop, it makes a query for each user rather than using the prefetched users
prompt.entries.approved.each do |entry|
puts entry.user.id
end
How do I rewrite this so that it doesn't do a query for each iteration of the loop?
It does a query for each user because entries.approved is calling the query *where('is_approved')* for each entry. Your find statement is merely pulling all of the prompts and creating objects with access to their child attributes. I think what you need is a where statement that selects all of the entries that have the attribute 'is_approved' and then run through a loop printing their ids.
Maybe try something like #entries = Entry.where(is_approved: true).includes(entries: user) )
Then
entries.user.each do |user|
puts user.id
end
Hope thats helps.

Creating a news feed in Rails 3

I want to create an activity feed from recent article and comments in my rails app. They are two different types of activerecord (their table structures are different).
Ideally I would be able to create a mixed array of articles and comments and then show them in reverse chronological order.
So, I can figure out how to get an array of both articles and comments and then merge them together and sort by created_at, but I'm pretty sure that won't work as soon as I start using pagination as well.
Is there any way to create a scope like thing that will create a mixed array?
One of the other problems for me, is that it could be all articles and it could be all comments or some combination in between. So I can't just say I'll take the 15 last articles and the 15 last comments.
Any ideas on how to solve this?
When I've done this before I've managed it by having a denormalised UserActivity model or similar with a belongs_to polymorphic association to an ActivitySource - which can be any of the types of content that you want to display (posts, comments, up votes, likes, whatever...).
Then when any of the entities to be displayed are created, you have an Observer that fires and creates a row in the UserActivity table with a link to the record.
Then to display the list, you just query on UserActivity ordering by created_at descending, and then navigate through the polymorphic activity_source association to get the content data. You'll then need some smarts in your view templates to render comments and posts and whatever else differently though.
E.g. something like...
user_activity.rb:
class UserActivity < ActiveRecord::Base
belongs_to :activity_source, :polymorphic => true
# awesomeness continues here...
end
comment.rb (post/whatever)
class Comment < ActiveRecord::Base
# comment awesomeness here...
end
activity_source_observer.rb
class ActivitySourceObserver < ActiveRecord::Observer
observe :comment, :post
def after_create(activity_source)
UserActivity.create!(
:user => activity_source.user,
:activity_source_id => activity_source.id,
:activity_source_type => activity_source.class.to_s,
:created_at => activity_source.created_at,
:updated_at => activity_source.updated_at)
end
def before_destroy(activity_source)
UserActivity.destroy_all(:activity_source_id => activity_source.id)
end
end
Take a look at this railscast.
Then you can paginate 15 articles and in app/views/articles/index you can do something like this:
- #articles.each do |article|
%tr
%td= article.body
%tr
%td= nested_comments article.comment.descendants.arrange(:order => :created_at, :limit => 15)
This assumes the following relations:
#app/models/article.rb
has_one :comment # dummy root comment
#app/models/comment.rb
belongs_to :article
has_ancestry
And you add comments to an article as follows:
root_comment = #article.build_comment
root_comment.save
new_comment = root_comment.children.new
# add reply to new_comment
new_reply = new_comment.children.new
And so forth.

Should I drop a polymorphic association?

My code is still just in development, not production, and I'm hitting a wall with generating data that I want for some views.
Without burying you guys in details, I basically want to navigate through multiple model associations to get some information at each level. The one association giving me problems is a polymorphic belongs_to. Here are the most relevant associations
Model Post
belongs_to :subtopic
has_many :flags, :as => :flaggable
Model Subtopic
has_many :flags, :as => :flaggable
Model Flag
belongs_to :flaggable, :polymorphic => true
I'd like to display multiple flags in a Flags#index view. There's information from other models that I want to display, as well, but I'm leaving out the specifics here to keep this simpler.
In my Flags_controller#index action, I'm currently using #flags = paginate_by_sql to pull everything I want from the database. I can successfully get the data, but I can't get the associated model objects eager-loaded (though the data I want is all in memory). I'm looking at a few options now:
rewrite my views to work on the SQL data in the #flags object. This should work and will prevent the 5-6 association-model-SQL queries per row on the index page, but will look very hackish. I'd like to avoid this if possible
simplify my views and create additional pages for the more detailed information, to be loaded only when viewing one individual flag
change the model hierarchy/definitions away from polymorphic associations to inheritance. Effectively make a module or class FlaggableObject that would be the parent of both Subtopic and Post.
I'm leaning towards the third option, but I'm not certain that I'll be able to cleanly pull all the information I want using Rails' ActiveRecord helpers only.
I would like insight on whether this would work and, more importantly, if you you have a better solution
EDIT: Some nit-picky include behavior I've encountered
#flags = Flag.find(:all,:conditions=> "flaggable_type = 'Post'", :include => [{:flaggable=>[:user,{:subtopic=>:category}]},:user]).paginate(:page => 1)
=> (valid response)
#flags = Flag.find(:all,:conditions=> ["flaggable_type = 'Post' AND
post.subtopic.category_id IN ?", [2,3,4,5]], :include => [{:flaggable=>
[:user, {:subtopic=>:category}]},:user]).paginate(:page => 1)
=> ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :flaggable
Don't drop the polymorphic association. Use includes(:association_name) to eager-load the associated objects. paginate_by_sql won't work, but paginate will.
#flags = Flag.includes(:flaggable).paginate(:page => 1)
It will do exactly what you want, using one query from each table.
See A Guide to Active Record Associations. You may see older examples using the :include option, but the includes method is the new interface in Rails 3.0 and 3.1.
Update from original poster:
If you're getting this error: Can not eagerly load the polymorphic association :flaggable, try something like the following:
Flag.where("flaggable_type = 'Post'").includes([{:flaggable=>[:user, {:subtopic=>:category}]}, :user]).paginate(:page => 1)
See comments for more details.
Issues: Count over a polymorphic association.
#flags = Flag.find(:all,:conditions => ["flaggable_type = 'Post' AND post.subtopic.category_id IN ?",
[2,3,4,5]], :include => [{:flaggable => [:user, {:subtopic=>:category}]},:user])
.paginate(:page => 1)
Try like the following:
#flags = Flag.find(:all,:conditions => ["flaggable_type = 'Post' AND post.subtopic.category_id IN ?",
[2,3,4,5]], :include => [{:flaggable => [:user, {:subtopic=>:category}]},:user])
.paginate(:page => 1, :total_entries => Flag.count(:conditions =>
["flaggable_type = 'Post' AND post.subtopic.category_id IN ?", [2,3,4,5]]))

Ruby-on-Rails: How to pull out most recent entries from a limited subset of a database table

Imagine something like a model User who has many Friends, each of who has many Comments, where I'm trying to display to the user the latest 100 comments by his friends.
Is it possible to draw out the latest 100 in a single SQL query, or am I going to have to use Ruby application logic to parse a bigger list or make multiple queries?
I see two ways of going about this:
starting at User.find and use some complex combination of :join and :limit. This method seems promising, but unfortunately, would return me users and not comments, and once I get those back, I'd have lots of models taking up memory (for each Friend and the User), lots of unnecessary fields being transferred (everything for the User, and everything about the name row for the Friends), and I'd still have to step through somehow to collect and sort all the comments in application logic.
starting at the Comments and using some sort of find_by_sql, but I just can't seem to figure out what I'd need to put in. I don't know how you could have the necessary information to pass in with this to limit it to only looking at comments made by friends.
Edit: I'm having some difficult getting EmFi's solution to work, and would appreciate any insight anyone can provide.
Friends are a cyclic association through a join table.
has_many :friendships
has_many :friends,
:through => :friendships,
:conditions => "status = #{Friendship::FULL}"
This is the error I'm getting in relevant part:
ERROR: column users.user_id does not exist
: SELECT "comments".* FROM "comments" INNER JOIN "users" ON "comments".user_id = "users".id WHERE (("users".user_id = 1) AND ((status = 2)))
When I just enter user.friends, and it works, this is the query it executes:
: SELECT "users".* FROM "users" INNER JOIN "friendships" ON "users".id = "friendships".friend_id WHERE (("friendships".user_id = 1) AND ((status = 2)))
So it seems like it's mangling the :through to have two :through's in one query.
Given the following relationships:
class User < ActiveRecord::Base
has_many :friends
has_many :comments
has_many :friends_comments, :through => :friends, :source => :comments
end
This statement will execute a single SQL statement. Associations essentially create named scopes for you that aren't evaluated until the end of the chain.
#user.friends_comments.find(:limit => 100, :order => 'created_at DESC')
If this is a common query, the find can be simplified into its own scope.
class Comments < ActiveRecord::Base
belongs_to :user
#named_scope was renamed to scope in Rails 3.2. If you're working
#if you're working in a previous version uncomment the following line.
#named_scope :recent, :limit => 100, : order => 'created at DESC'
scope :recent, :limit => 100, :order => 'created_at DESC'
end
So now you can do:
#user.friends_comments.recent
N.B.: The friends association on user may be a cyclical one through a join table, but that's not important to this solution. As long as friends is a working association on User, the preceding will work.