Reputation: 1203
First let me apologize for a seemingly easy question, but being new to rails, ruby and programming I feel like I've exhausted the "New to Rails" tutorials out there.
Here's what I'm up against.
I have a Users model and Institution Model that have a "has_many :through => :company_reps" relationship.
The user has basic fields (name, email, password) (I'm using devise)
The Institution has many fields but the relevant ones are (client = boolean, lead = boolean, demo_date = date/time) To complicate it further each Institution can have one or two users but most only have one.
We are holding a contest for the users and I need to award points to each user based on the demo_date field and client field.
So firstly what I need to do is give each user 10 points that is related to an institution that is a client, unless that institution has 2 users in which case I need to give those two users 5 points each.
Secondly I need to give all users 1 point that are related to an institution that has a demo date after Feb. 2012.
I'm using Ruby 1.9.2, Rails 3.2.8 and MySQL
As always thank you for the help.
MySQL Institution Info
CREATE TABLE `institutions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`state_id` int(11) DEFAULT NULL,
`company` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`clientdate` datetime DEFAULT NULL,
`street` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`city` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`zip` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`source` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`source2` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`demodate1` datetime DEFAULT NULL,
`demodate2` datetime DEFAULT NULL,
`demodate3` datetime DEFAULT NULL,
`client` tinyint(1) DEFAULT NULL,
`prospect` tinyint(1) DEFAULT NULL,
`alead` tinyint(1) DEFAULT NULL,
`notcontacted` tinyint(1) DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7805 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Institution Model
class Institution < ActiveRecord::Base
attr_accessible :company, :phone, :assets, :clientdate, :street, :city, :state_id, :zip, :source, :source2, :demodate1, :demodate2, :demodate3, :client, :prospect, :alead, :notcontacted
belongs_to :state
has_many :users, :through => :company_reps
has_many :company_reps
end
User Model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :first_name, :last_name
# attr_accessible :title, :body
has_many :states, :through => :rep_areas
has_many :institutions, :through => :company_reps
has_many :rep_areas
has_many :company_reps
def name
first_name + " " + last_name
end
end
Company Rep Model
class CompanyRep < ActiveRecord::Base
belongs_to :user
belongs_to :institution
end
Upvotes: 0
Views: 169
Reputation: 6644
Update (since my first attempt was wrongly assuming User has_one :institution
The simplest option would be to do the basic calculation on the Institution
model to establish how many points the institution is "worth", and then sum that value to calculate the user's points.
# Institution
def points
points_for_client + points_for_demo_date
end
private
def points_for_client
if client?
10 / users.count
else
0
end
end
def points_for_demo_date
if demo_date.present? && demo_date >= Date.new(2012, 3, 1)
1
else
0
end
end
Note that you could condense those if
statements into one-liners with the ternary operator ? :
if you prefer. Also note that I assumed "after February" to mean "from March 1 onwards".
The check for a nil demo_date
is also a matter of taste. Take your pick from
# Verbose, but IMO intention-revealing
demo_date.present? && demo_date >= Date.new(...)
# Perhaps more idiomatic, since nil is falsy
demo_date && demo_date >= Date.new(...)
# Take advantage of the fact that >= is just another method
# Concise, but I think it's a bit yuk!
demo_date.try :>=, Date.new(...)
Now that each institution is worth a certain number of points, it's fairly simple to sum them up:
# User
def points
institutions.inject(0) {|sum, institution| sum + institution.points }
end
Check out the docs for inject
if you're not familiar with it, it's a nifty little method.
As far as performance goes, this is suboptimal. A basic improvement would be to memoize the results:
# Institution
def points
@points ||= points_for_client + points_for_demo_date
end
# User
def points
@points ||= institutions.inject ...
end
so that further calls of points
in the same request don't recalculate the value. That's ok as long as the client
and demo_date
don't change while the User
object is still alive:
some_user.points #=> 0
some_user.institution.client = true
some_user.points #=> 0 ... oops
The User
object will be recreated the next request, so this might not be a problem (it depends on how these fields change).
You could also add a points
field to the User
and thus save the value in the database, using the original version as an update_points
method instead
def update_points
self.points = institutions.inject ...
end
However, working out when to recalculate the value is then going to be an issue.
My suggestion would be to keep it as simple as possible and avoid premature optimization. It's a relatively simple calculation so it's not going to be a big performance issue, as long as you don't have a huge number of users and institutions or a lot of requests going on.
Upvotes: 2
Reputation: 5453
Points accumulate to User
s, so it would seem to make sense to add a method call on the User
class which returns the number of points they've accumulated.
I'd start on this by just writing a method that computed the total points each time it's called, with some unit tests to make sure the computation is right. I wouldn't save the results at first - depending on how many objects you have, how often you need to compute points, etc, you may not need to save it at all.
Upvotes: 0