How much logic should I add to my ActiveRecords

I have a basic stylistic question I would like some feedback on.

I have a table containing messages, each message has a status of current or expired.

Messages expire a set period after they were created. Right now I have a function in my controller to get the current message. This function also has the expiration logic built into it.

Get Current   msg = Message that is marked current in the DB.   if msg == nil     return nil   end   if msg.hasExpired?     Set message to expired in DB     return nil   else     return msg   end end

So I was thinking it would be useful to put this in my model. I could add a method to the model called getCurrent that had this exact logic. I could also put the expiration logic in an after_find callback.

But this seems like business logic so I'm not sure this is a good idea? Should this stay in the controller, if I put in the model what approach is best.

Expiring messages is certainly business logic so it should go in the model not the controller. You could put the code in an after_find callback in the model so it is run every time a record is fetched.

However if the decision as to whether a message is expired or not is entirely based on the time since creation there may be no need for a column in the database. Simple add a method to the model, something like def expired?   Time.now - self.created_at > 10.days # or whatever the period is end

Colin

def expired? Time.now - self.created_at > 10.days # or whatever the period is end

fwiw, I prefer to write that sort of condition as:

Yes, much better, I had forgotten about ago.

Colin

Thanks for that, I simplified things a bit for the post. There is actually a reason to have an explicit status.

I'm going ahead with adding the after_find callback but I think I've run into another issue.

I was thinking I would do the following in after_find when I detected an expired message.

Change its status to expired. Save It Return false.

I was thinking returning false would result in the find not returning that result. But it still seems to be returning it. I couldn't find anything online that really explained what is supposed to happen when you return false in this callback. Any ideas?

Thanks for that, I simplified things a bit for the post. There is actually a reason to have an explicit status.

I'm going ahead with adding the after_find callback but I think I've run into another issue.

I was thinking I would do the following in after_find when I detected an expired message.

Change its status to expired. Save It Return false.

I was thinking returning false would result in the find not returning that result. But it still seems to be returning it. I couldn't find anything online that really explained what is supposed to happen when you return false in this callback. Any ideas?

I presume here you are trying to get round the problem that if a find is performed with a condition of !expired then the find should somehow reject the record after setting it to expired. I had not thought of that complication. In particular if the find is for a set of records then the changed records must be removed from the collection. I don't know how to do that.

Any suggestions from the more experienced here?

Colin

It may sound silly simple, but why not just defined a named scope for that and be done with it.

Assuming you are on Rails 3 something like this should work:

scope :active, lambda {where([“created_at > ?”, 10.days.ago])}

It may sound silly simple, but why not just defined a named scope for that and be done with it. Assuming you are on Rails 3 something like this should work: scope :active, lambda {where(["created_at > ?", 10.days.ago])}

I tend to agree, it is much simpler to do it this way rather than having an explicit status. It is also poor database design as you have the same information stored twice in the database (once in the explicit status and once in created_at). If you need an explicit status then just provide a method called expired which returns the state based on created_at. To the code calling this it is virtually indistinguishable from a value stored in the database.

Colin

If it's absolutely necessary to have an explicit status, I'd typically do this with a cron script that expires messages at a sensible frequency (nightly, perhaps?) as expiring them on-load (as you've noticed) is going to make an epic mess - not only will collection finds not work right, but simple stuff like "count the number of active messages" will be harder than needed.

--Matt Jones

Chirag Singhal <chirag.singhal@...> writes:

It may sound silly simple, but why not just defined a named scope for that

and be done with it.

Assuming you are on Rails 3 something like this should work:

scope :active, lambda {where(["created_at > ?", 10.days.ago])}

Bingo!

Although given what he seems to be attempting here I would make it the default scope:

scope :default, lambda {where(["created_at > ?", 10.days.ago])}

If you want to search for expired messages, just add another scope:

scope :expired, lambda {where(["created_at < ?", 10.days.ago])}

I am not sure about that, but have not got time to test it now. If you have a default scope and then add another will not the :expired scope be applied *as well as* the default, resulting in no records found at all.

Also, even if the above does work I think one of the scopes should include the exact equality, otherwise records that are exactly 10.days.ago will not be found by either (not that there will be many as it must be exact to the second I think).

Colin

Also, you can’t define default scope with lambda until Rails 3.1 You can use “unscoped” if you want to override default scope