MVC concept was described in 1979 by Trygve Reenskaugom and despite the fact that the first reviews were not very positive, it finds its use to this day. Concept improved and complemented with new followers, depending on the capabilities of the platform and language, but the essence remains the same - to share data submission and processing of user actions on the individual components. Features of this separation become reuse and loose coupling components. But the more we dive into the Rails more feel imperfection MVC. This gem provides for you tools for extending Rails's MVC by next components:
- ModelQuery
- ModelPresenter
- ModelServiceObject
- Responder
- UseCase
- ParameterSanitizer
- Validator
At the next picture presented class diagram of my concept:
Add this line to your application's Gemfile:
gem 'alter_mvc', '0.1.0'
And then execute:
$ bundle
Or install it yourself as:
$ gem install alter_mvc
A model's wrapper for run some specific queries on it. For adding query object run following command:
$ rails generate alter_mvc:query User
And include AlterMvc::Concerns::Querable to appropriate model:
include AlterMvc::Concerns::Querable
In this system acts as presenter providing access view to model. ModelPresenter provides defining method for fetching information from non-direct attributes (associations) and self-rendering feature. For example, Post may has many view: small block on main page, as a line on list page and post's show page. Rendering post's in this situation may be done like this:
post.rendering as: :small_block, with: self #for small block on main
page
post.rendering as: :line, with: self #for line on list page
post.rendering as: :full_show, with: self #for post's show page
ModelPresenter also provides collection rendering feature:
posts.rendering as: :line, with: self
equivalents to:
posts.each { |post| post.rendering(as: :line, with: self) }
For self-rendering from example above you must adding templates to app/views/posts/render with names _line.html.slim, small_block.hmtl.slim, _full_show.html.slim.
For adding presenter object run:
$ rails generate alter_mvc:presenter Post
And include AlterMvc::Concerns::Presentable to appropriate model:
include AlterMvc::Concerns::Presentable
Contains model specific business rules. Run following command for adding service object:
$ rails generate alter_mvc:service_object Post
And include AlterMvc::Concerns::ServiceObjectable to appropriate model:
include AlterMvc::Concerns::ServiceObjectable
Contains feature specific business rules. Extracting all logic to UseCase makes controller thin and cleared. UseCases seems like as scenario in USE-CASE diagram.
For adding new empty use case class run:
$ rails generate alter_mvc:use_case AddPost
Make decision about what template will rendered and setting flash messages according to object state. For example, lets see next code:
class BookResponder < BasicResponder
def create_js
unless has_errors?
controller.flash[:notice] = "Success booking!"
render json: { status: :ok }
else
render json: { status: :error, errors: resource.errors.full_messages }
end
end
end
class BooksController < ApplicationController
self.responder = BookResponder
def create
@book = Booking::AcceptOffer.new.run book_params
respond_with @book
end
end
Using Responder give to us some advantages:
- Controller is so clear and using only for running specific use case and responding with it's result
- More testable, DRY and SRP approach
For adding new responder run:
$ rails generate alter_mvc:responder Book
and add this line to appropriate controller:
class BooksController < ApplicationController
self.responder = BookResponder
end
Parsing income GET and POST parameters for use cases and provides objecting approach for accessing it. This gives single place for fetching income parameters and ability for using dependency injection when passing ParameterSanitizer object to UseCase.
For adding new ParameterSanitizer run:
$ rails generate alter_mvc:parameter_sanitizer PlacingBet
Moving validation in additional layer helps us to hard-line divide between persistence layer and the actual domain logic.
For adding new Validator run:
$ rails generate alter_mvc:validator DraftPost
- Added ParameterSanitizer layer
- Added Validator layer
- Passing index to partial when rendering collection
- Added options to collection rendering
- Fixed bugs, refactoring and testing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request