If I can overly generalize, and as a person who generally loves Ruby and Rails, I'll suggest that there are two schools of Rails app development emerging.<p>The first is what I'll term the "Classic School" or "DHH School" characterized by full-blown utilization of all Rails magic such as AR/AC callbacks, pretty skinny but not obsessively skinny controllers, an eschewance of service objects and more.<p>The "Emerging School" relies less on some aspects of Rails magic, and introduces patterns like services/interactors/DCI and seems to be more focused on building a domain models which attempt to be less coupled to the framework.<p>I think the Grouper folks are in the second camp given their previous blog post about interactors which showed up here on HN. Also, I think the desire to encapsulate authorization into a reusable object seems slightly more in the spirit of the emerging school.<p>What I find odd is that they'd offer a solution for authorization which is so bound up in Rails magic. It seems to me authorization is really part of the domain model and as such you would expect to find that code in whatever gets called by the controller (which I would expect to be something like an interactor, but in this blog post is just plain old Rails code.)
Ensuring that the Ticket is valid is obviously a domain model concern. Policy objects can be a fine idea when they're swappable and you need to allow for multiple different policies. This is not one of those cases.<p>Here's a much simpler approach that keeps the validation logic in the domain model and uses features that Rails has had since the dawn of the framework: <a href="https://gist.github.com/dhh/9672827" rel="nofollow">https://gist.github.com/dhh/9672827</a>
Oh boy, is DHH going to jump into this too?<p>As an intermediate Rails developer...I don't understand why this if/else forest:<p><pre><code> if user.blacklisted? # They've been banned for bad-behaviour
fail! “You can't book a Grouper at this time”
elsif grouper.full?
fail! “This grouper is full, please pick another date”
elsif grouper.past?
fail! “This grouper has already occured!”
elsif user.has_existing_grouper?(grouper)
fail! “You’re already going on a Grouper that day”
elsif ticket.confirmed?
fail! “You’ve already confirmed this grouper”
end
</code></pre>
-- can't simply be part of the Ticket object? The Ticket clearly has a relation to User and Grouper and talking to those objects (i.e. `if user.not_blacklisted? && user.not_booked(self.date)`) don't violate Demeter...how is making a policy object cleaner than just using a Concern that is included into Ticket? The controller then just needs to ask for `ticket.confirmable?`<p>edit: OK, not ask for `ticket.confirmable?`, but rather, try to `save` the ticket and the Ticket constructor/validators can pass on the errors to the controller.
How is a Policy different than an ActiveRecord::Validator? Could this TicketPolicy not just be a validator extracted to it's own file (and tested on it's own)?<p><a href="http://api.rubyonrails.org/classes/ActiveModel/Validator.html" rel="nofollow">http://api.rubyonrails.org/classes/ActiveModel/Validator.htm...</a>
related, Pundit provides some similar functionality in a very minimal package <a href="https://github.com/elabs/pundit" rel="nofollow">https://github.com/elabs/pundit</a>
While more interesting than the first part, it should be noted that the policy does not solve the same problem as the one they had originally, where different fail reasons led to different URLs.<p>If you only intend to redirect to one url in case of fail, then using normal validations (possibly on a service object implementing ActiveModel::Validations will suffice and produce as simple code as their solution.<p>Which isn't saying that I dislike it, only that it is quite equivalent with a separate service model without controller integration.
I would use CanCan ability.rb file for that: <a href="https://github.com/CanCanCommunity/cancancan" rel="nofollow">https://github.com/CanCanCommunity/cancancan</a>
I think this is a responsibility that lies in the model. What if you decide to allocate tickets through a different model? You have to remember to include this policy everywhere. Another approach i think is reasonable is creating a class for this on the model level.<p><a href="https://gist.github.com/jvans1/9745395" rel="nofollow">https://gist.github.com/jvans1/9745395</a>