I recently implemented a paywall too and I have the following advice.
- First, your User class can quickly get clogged up when you combine too much into it. Consider separating account mgmt details into a separate model say an Account model. This way functionality about upgrades, downgrades, renewals, and expiration can be better encapsulated away from your core User model. Once you start tying in CanCan and Devise (if you're using that), User can quickly get overly multiplexed -- keep the central purpose of User as singular as possible. I wish I had done this sooner: I had to refactor into Account eventually and it's much cleaner (more testable, simpler, etc.) now. It's a simple has-a/belongs-to relationship.
- With a separate Account (or Subscription) class your controller can perhaps be easier to manage? TDD tests might help reveal that as you work through the use cases.
- Keep your expiration date simple. It is reasonably cheap to check the date on many requests as long as your only rely on local data. If you use a external paywall (say Stripe or Recurly) you'll want to minimize checking and it depends on how close to the original time of day you care about. I didn't see the need for a daily task, although that could certainly work, but then you'd have to verify it ran correctly every day.
- I just used a separate account_type field (say, basic vs premium) to help support logic around expiration dates which is the same as what you're doing with ROLES. Switching back and forth then is reasonably simple and supports expiration date logic. Given what you're doing though be prepared to refactor as your platform's concept of Roles becomes more sophisticated or multi-dimensional (another reason to separate Account now). I used CanCan just to tie to the role without any regard to expiration. Keep the roles simple and let expiration logic handle switching roles as necessary. Again, separating expiration and account data away from user can help this. We largely separated admin away from paid accounts since in a way, they can be seen as different "dimensions" but that's just a design choice based on the dynamics of our specification.
Hope this helps.
UPDATE: 3/18/14 I realize your subject refers to Devise. Is there a more specific question you have. My implementation also involved Devise, so perhaps I can give more targeted advise.