This week in Rails
Tags: ruby, testing, exceptions, rails
Published: 2020-03-29 00:58:38

This week I've made use of different ruby tools that help me make my tests, controllers and business application clean and DRY. I will show you a bit of what you can do with custom contexts for active record validations, service objects and custom exceptions to reduce your controller's methods [AbcSize](https://www.rubydoc.info/gems/rubocop/0.27.0/RuboCop/Cop/Metrics/AbcSize) and use RSpec shared example groups to keep your tests code DRY.

##Active Record Validations
We can specify when the validation should happen with the `:on` option. With custom context those validations are triggered explicitly by passing the contexts name to `valid?` or `save`
```ruby
#app/models/person.rb
validates :age, presence: true, on: :submit

#app/somewhere_else.rb
Person.valid?(:submit) 

Person.save(context: :submit)
```
[More info.](https://guides.rubyonrails.org/active_record_validations.html#on)

##Service Objects
Service objects are helpful to extract your business logic from your controllers. They must perform one action, e.g under `app/services/app_manager` folder you can group your needed actions per file as `app_creator.rb` or `app_submitter.rb`. Then, you can use those classes in your controllers.
```ruby
app = AppManager::AppCreator.call(params)

AppManager::AppSubmitter.call(app) if params['submit']
``` 
I recommend reading this ebook [Fearless Refactoring.](https://rails-refactoring.com/)

##Testing
`shared_example_group` RSpec feature lets you group specifics behaviors that are repeated in your tests. This was useful to test API's responses with `it_behaves_like 'a success response'` within many API controllers.

[More info.](https://relishapp.com/rspec/rspec-core/v/2-0/docs/example-groups/shared-example-group)

##Exceptions
You can create and raise your custom exceptions to reduce if's and else's cases in your app business logic. Instead of dealing with `nil` or `false` values returned from your methods, you can raise your custom exceptions to keep your code clean and maintainable.
So you can transform your code from:
```ruby
def create
...
unless current_user.can_book_from?(agency)
  redirect_to trip_reservations_page, notice: "You're not allowed to book from this agency."
end
unless trip.has_free_tickets?
  redirect_to trip_reservations_page, notice: "No free tickets available"
end
unless reservation.save
  logger.info "Failed to save reservation: #{reservation.errors.inspect}"
  redirect_to trip_reservations_page, notice: "Reservation error."
end
...
```
To:
```ruby
def create
  TripReservationService.call(current_user, params[:trip_reservation])
rescue TripReservationService::NotAllowedToBook
  redirect_to trip_reservations_page, notice: "You're not allowed to book from this agency."
rescue TripReservationService::NoTicketsAvailable
  redirect_to trip_reservations_page, notice: "No free tickets available."
end
```

I recommend reading this ebook [Mastering Ruby Exceptions by Honey Badger](https://honeybadger.leadpages.co/exceptions/)

Links:
- [Rubocop AbcSize Docs](https://www.rubydoc.info/gems/rubocop/0.27.0/RuboCop/Cop/Metrics/AbcSize)
- [Active Record Validations Docs](https://guides.rubyonrails.org/active_record_validations.html#on)
- [Fearless Refactoring Ebook](https://rails-refactoring.com/)
- [Rspec Docs shared example groups](https://relishapp.com/rspec/rspec-core/v/2-0/docs/example-groups/shared-example-group)
- [Mastering Ruby Exceptions by Honey Badger](https://honeybadger.leadpages.co/exceptions/)