1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
<% myid = self.current_user.id %> Messages:<br /><br /> <% my_messages = Message.find(:all, :conditions => "user_id = #{myid}") %> <% mcount = 0 prcount = 0 iecount = 0 %> <% @my_messages.each do |i| %> <% senders_usertype = User.find(i.sender_id).usertype %> <% case senders_usertype when 'Industry Expert' iecount = iecount + 1 when 'Media' mcount = mcount + 1 when 'PR' prcount = 'prcount + 1' else # nada end %> <% end %> <%= mcount %> <br /> <%= prcount %> <br /> <%= iecount %> <br />
Refactorings
No refactoring yet !
Derrick
January 10, 2008, January 10, 2008 21:52, permalink
I'm not an expert on this but to start with I think you want to link the tables, so you'll have a column in in your messages table that is something like user_id or recipient_id, and then you'll just reference them like follows. I'm new to ruby, but I'm pretty sure thats how it would be done. If I can find the code in my app before anyone else answers i'll post it.
1 2 3 4 5 6
<% for user in @users -%> <p><%= user.name %> got <%= user.messages.size %> messages</p> <% end -%>
nootopian
January 10, 2008, January 10, 2008 23:07, permalink
Thanks Derrick,
Apologies I probably should have provided more info.
I do have a user_id column in the messages table along with a sender_id.
In the user model I have
has_many :messages
I can do things like
User.find(12).messages
Message.count(:conditions => "user_id=12")
It just gets tricky when I want to count the total of messages sent to a recipient by usertype
Jeremy Weiskotten
January 11, 2008, January 11, 2008 00:19, permalink
You should let ActiveRecord wire your objects together instead of using finders to fetch them. You should also typically avoid calling finders in your view templates.
Instead of:
myid = self.current_user.id
my_messages = Message.find(:all, :conditions => "user_id = #{myid}")
you can use:
my_messages = current_user.messages
If the page is only going to display these counts, you should use the current_user.messages.count method. This will be more efficient than fetching the messages and each sender from the database. You really should move this count logic into the controller to clean up the view. Set the counts to variables like @count_messages_from_media, and use those variables in your view.
1 2 3 4 5 6 7
<p>Messages:</p> <p> <%= current_user.messages.count(:conditions => ['sender_id in (select id from users where usertype=?)', 'Industry Expert']) %><br /> <%= current_user.messages.count(:conditions => ['sender_id in (select id from users where usertype=?)', 'Media']) %><br /> <%= current_user.messages.count(:conditions => ['sender_id in (select id from users where usertype=?)', 'PR']) %> </p>
nootopian
January 11, 2008, January 11, 2008 08:52, permalink
Thats awesome. Thanks so much. I learnt a heap from that.
I had to change :condition to :conditions BTW
Casper
January 11, 2008, January 11, 2008 08:59, permalink
Not going to comment on the Rails parts (you should move more code into the Model, and have less in your view). Just the "rubyism" part.
Since you have all the cross references in your models you should be able to access all messages for a user by saying user.messages, and access senders of messages by doing message.sender.
Then we group the usertype counters into a hash, so we don't have to hard-code all that stuff and the code stays flexible and "adapts" when you add new user types.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
# Get all messages for the current user messages_for_user = current_user.messages # Initialize our hash where we store the counters message_type_counters = Hash.new # Loop through the messages messages_for_user.each do |msg| # Grab the usertype of the sender usertype = msg.sender.usertype # Make sure the hash element is initialized message_type_counters[usertype] ||= 0 # Add to the counter message_type_counters[usertype] += 1 end # Display the results message_type_counters.each do |usertype, count| puts "#{usertype}: #{count}" end
Casper
January 11, 2008, January 11, 2008 09:51, permalink
Or..come to think of it. This is a perfect place for an SQL JOIN. Databases love to do these kinds of things. And they do it fast. Here's a good place to read up on joins:
http://www.w3schools.com/sql/sql_join.asp
Solution now becomes:
1 2 3 4 5 6 7 8 9 10 11 12 13
# Untested since I don't have your database, but it should be # something like this: grouped_count = Messages.count(:conditions => "user_id = #{current_user.id}", :joins => "join users on sender_id = users.id", :group => "usertype") # You will get back an ActiveSupport::OrderedHash # which you can loop like this: grouped_count.each do |usertype, count| puts "#{userype}: #{count}" end
fifoo
January 11, 2008, January 11, 2008 12:57, permalink
Not sure if you still need it but I thought I would post it anyway.
That's how I would do it using ActiveRecord features but I agree with Casper that if you don't need the messages, you probably want to rely on SQL for efficiency.
And I would definitely put most of the stuff in the Models.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# User model class User has_many :messages has_one :type def number_of_messages_from_usertype(type) messages.select { |m| m.sender.type == type }.size end end # Message model class Message belongs_to :recipient, :class_name => "User", :foreign_key => "user_id" belongs_to :sender, :class => "User", :foreign_key => "sender_id" end
rpheath
January 11, 2008, January 11, 2008 19:25, permalink
This is the perfect opportunity for an association method. Usually, if you say aloud what you are wanting to do, you can pretty much express that exact thing in Ruby/Rails. So, you are "wanting a count of the number of messages per user, per (dynamic) type".
Here's how I'd do it... Note: you can flop the options={} so that the default is to return a count (so you wouldn't have to pass :count_only => true each time), but by doing this you can also return the messages themselves as a set in case you need that separately, too. I just don't know which has higher precedence: count or message set. In the code below, the association method defaults to return the objects, and the helper defaults to return the count, so you can see how it might work from both perspectives.
user.rb
1 2 3 4 5 6 7 8 9
class User has_many :messages do def by_type(type, options={}) options[:count_only] ||= false messages = find(:all, :conditions => [ 'type = ?', type ]) options[:count_only] ? messages.size : messages end end end
OPTION 1: without helper in view
1 2 3 4 5 6 7
<h1>Messages:</h1> <ul> <li>Industry Expert: <%= current_user.messages.by_type('Industry Expert', :count_only => true) -%></li> <li>Media: <%= current_user.messages.by_type('Media', :count_only => true) -%></li> <li>PR: <%= current_user.messages.by_type('PR', :count_only => true) -%></li> </ul>
OPTION 2: with helper in view
1 2 3 4 5 6 7
<h1>Messages:</h1> <ul> <li><%= message_count_for 'Industry Expert' -%></li> <li><%= message_count_for 'Media' -%></li> <li><%= message_count_for 'PR' -%></li> </ul>
application_helper.rb (or users_helper.rb / messages_helper.rb, depending)
1 2 3 4
def message_count_for(type, options={}) options[:count_only] ||= true "#{type}: #{current_user.messages.by_type(type, options[:count_only])}" end
nootopian
January 12, 2008, January 12, 2008 16:22, permalink
Thanks so much everyone.
I came across this recently
http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
which is helpful to me in determining where in the MVC to put code.
nootopian
January 12, 2008, January 12, 2008 16:26, permalink
@Ryan, thanks for solving my next problem too! (displaying messages by usertype of sender)
Jeremy Weiskotten
January 12, 2008, January 12, 2008 21:18, permalink
To improve on Ryan's refactoring, you can use the following for better performance when you only want counts.
1 2 3 4 5 6 7 8 9 10 11 12
class User has_many :messages do def by_type(type, options={}) conditions = [ 'type = ?', type ] if options[:count_only] count(:conditions => conditions) else find(:all, :conditions => conditions) end end end end
Hi,
I have a users users table and a messages table. The users table has a column 'usertype'.
I am trying to count the total messages sent to a recipient(they are logged in) by usertype.
It works but Im sure its a dumb way to be doing it.