This is an example application to cover the basics of using Granite in a Rails app. For detailed documentation, check out the official docs.
Clone the repository:
git clone https://github.com/toptal/example_granite_application.git
Install Ruby and bundler if you haven't already and then:
cd example_granite_application
bundle install
bundle exec rails s
bundle exec rspec
In apq folder you can find the business action usage of Granite. The application samples just contain one real model persisted in the database.
The model persisted is Book:
> Book # => Book(id: integer, title: string)
BooksController uses the
action Ba::Book::Create combined with the
current_user
to insert the new book in the database.
And, with a logged user we create some books with a title.
In the controller, the action is instantiated with the current user as a performer and render feedback depending on action success.
class BooksController < ApplicationController
rescue_from Granite::Action::NotAllowedError do |exception|
redirect_to books_path, alert: 'You\'re not allowed to execute this action.'
end
# POST /books
def create
book_action = Ba::Book::Create.as(current_user).new(params.require(:book))
if book_action.perform
# render success
else
# render errors
end
end
# ...
end
The current action and controller are clean because the logic and important behavior from Ba::Book::BusinessAction is where the magic is:
class Ba::Book::BusinessAction < BaseAction
allow_if { performer.is_a?(User) }
represents :title, of: :subject
validates :title, presence: true
private
def execute_perform!(*)
subject.save!
end
end
Let's break down each macro and comprehend how it's being used:
You can expect only to execute some action with a user. For testing purposes, it's only verifying if the performer is a user.
allow_if { performer.is_a?(User) }
Testing it on the console:
Ba::Book::Create.as('Fake User').new.allowed? # => false
With a real user:
Ba::Book::Create.as(User.first).new.allowed? # => true
Remember you need to have at least one user to make this example work.
The book has a title:
represents :title, of: :subject
Instancing with the title attribute:
Ba::Book::Create.as(User.first).new(title: "My first book")
# => #<Ba::Book::Create title: "My first book">
You can also be validating your model:
validates :title, presence: true
And check if the action is valid or not:
Ba::Book::Create.as(User.first).new().valid? # => false
Ba::Book::Create.as(User.first).new(title: "My first book").valid? # => true
And, with a logged user we create some books with a title.
In the controller, the action instantiates with the current user as the performer and render feedback depending on the action response.
The subject
needs memoization because the record will be persistent in the action performed.
class Ba::Book::Create < BA::Book::BusinessAction
def subject
@subject ||= ::Book.new
end
end
While we have a special macro for subject
in the action
Ba::Book::Update, just referring it's a book.
class Ba::Book::Update < BA::Book::BusinessAction
subject :book
end
The method execute_perform
contains the real logic performed:
def execute_perform!(*)
subject.save!
end
Note that the title
is not being assigned because it's using
represents
and active_data
is doing the assignment behind the scenes.
The policies, preconditions, and validations can also block the perform
call
and from running the execute_perform
method.
BooksController uses rescue_from
to
encapsulate exceptions in case the policies are not satisfied.
rescue_from Granite::Action::NotAllowedError do |exception|
redirect_to books_path, alert: 'You\'re not allowed to execute this action.'
end
You can check more details about the entire application following the official documentation tutorial.