Readymade
This gems contains basic components to follow ABDI architecture
Tested with ruby:
- 3.1
- 3.0
- 2.7
Installation
Add this line to your application's Gemfile:
gem 'readymade'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install readymade
Usage
Inherit your components from:
Readymade::Response
Readymade::Form
Readymade::InstantForm
Readymade::Action
Readymade::Operation
Readymade::Response
response = Readymade::Response.new(:success, my_data: data)
response.success? # true
response = Readymade::Response.new(:fail, errors: errors)
response.success? # false
response.fail? # true
response.status # 'fail'
response.data # { errors: { some: 'errors' } }
Readymade::Form
Check more form features examples in lib/readymade/form.rb
class Orders::Forms::Create < Readymade::Form
PERMITTED_ATTRIBUTES = %i[email name category country customer]
REQUIRED_ATTRIBUTES = %i[email]
validates :customer, presence: true, if: args[:validate_customer]
end
order_form = Orders::Forms::Create.new(params, order: order, validate_customer: false)
order_form.valid? # true
form_options
# app/forms/my_form.rb
class MyForm < Readymade::Form
PERMITTED_ATTRIBUTES = %i[email name category country]
REQUIRED_ATTRIBUTES = %i[email]
def form_options
{
categories: args[:company].categories,
countries: Country.all
}
end
end
# app/controllers/items_controller.rb
def new
@form = MyForm.form_options(company: current_company)
end
/ app/views/items/new.html.slim
= f.select :category, collection: @form[:categories]
= f.select :country, collection: @form[:countries]
= f.text_field :email, required: @form.required?(:email) # true
= f.text_field :name, required: @form.required?(:name) # false
Readymade::InstantForm
Permit params and validates presence inline
Readymade::InstantForm.new(my_params, permitted: %i[name phone], required: %i[email]) # permits: name, phone, email; validates on presence: email
Readymade::Action
class Orders::Actions::SendNotifications < Readymade::Action
def call
send_email
send_push
send_slack
response(:success, record: record, any_other_data: data)
end
end
response = Orders::Actions::SendNotifications.call(order: order)
response.fail? # false
response.success? # true
response.data[:record]
response.data[:any_other_data]
.call_async
- any action could we call async (using ActiveJob)
class Orders::Actions::SendNotifications < Readymade::Action
def call
send_email
send_push
send_slack
response(:success, record: record, any_other_data: data)
end
...
end
Orders::Actions::SendNotifications.call_async(order: order)
Orders::Actions::SendNotifications.call_async!(order: order, queue_as: :my_queue) # job will be executed in 'my_queue'
# Important! Make sure your sidekiq configuration has 'my_queue' queue
.call!
- raise error unless response is success
(action must return Readymade::Response.new(:success))
class Orders::Actions::SendNotifications < Readymade::Action
def call!
send_email
return response(:fail, errors: errors) unless email_sent?
send_push
send_slack
response(:success, record: record, any_other_data: data)
end
...
end
Orders::Actions::SendNotifications.call!(order: order) # raise error if response is fail
.call
+ conisder_success: true
class Orders::Actions::SendNotifications < Readymade::Action
def call!
send_email
return response(:skip, consider_success: true) if skip_email?
send_push
send_slack
response(:success, record: record, any_other_data: data)
end
...
end
Orders::Actions::SendNotifications.call!(order: order) # does not raise error if skip_email? returns true
.call_async!
- runs in background and raise error unless response is success
(action must return Readymade::Response.new(:success))
class Orders::Actions::SendNotifications < Readymade::Action
def call!
send_email
return response(:fail, errors: errors) unless email_sent?
send_push
send_slack
response(:success, record: record, any_other_data: data)
end
...
end
Orders::Actions::SendNotifications.call_async!(order: order) # job will be failed
Readymade::Operation
Provides set of help methods like: build_form
, form_valid?
, validation_fail
, save_record
, etc.
class Orders::Operations::Create < Readymade::Operation
def call
build_record
build_form
return validation_fail unless form_valid?
assign_attributes
return validation_fail unless record_valid?
save_record
success(record: record)
end
end
Readymade::Controller::Serialization
class MyController < ApplicationController
include Readymade::Controller::Serialization
end
Serialization helpers for controllers. Dependencies that must be installed on your own:
Readymade::Model::ApiAttachable
Add base64 attachments format for your models
class User < ApplicationRecord
has_one_attached :avatar
has_many_attached :images
include Readymade::Model::ApiAttachable
# must be included after has_one_attached, has_many_attached declaration
# api_file = {
# base64: 'iVBORw0KGgoAAA....',
# filename: 'my_avatar.png'
# }
# record.avatar = api_file 🎉
end
copy spec/support/api_attachable.rb
def to_api_file(file)
{ base64: Base64.encode64(file.read), filename: file.original_filename }
end
# rspec example
let(:avatar) { Rack::Test::UploadedFile.new(Rails.root.join('spec/support/assets/test-image.png'), 'image/png') }
let(:params) { { user: attributes_for(:user).merge!(avatar: to_api_file(avatar)) } }
Readymade::Model::Filterable
class User < ApplicationRecord
include Readyamde::Model::Filterable
scope :by_status, ->(status) { where(status: status) }
scope :by_role, ->(role) { where(role: role) }
end
User.all.filter_collection({ by_status: 'active', by_role: 'manager' })
User.all.filter_collection({ by_status: 'active', by_role: 'manager' }, chain_with: :or) # active OR manager
Readymade::Model::ValidatableEnum
Instead of raised error when enum value is not valid, it adds error to the record
class User < ApplicationRecord
include Readymade::Model::ValidatableEnum
enum status: { inactive: 0, active: 10 }
enum role: { customer: 0, admin: 10 }
validatable_enum :status, :role
end
user = User.new(status: 'archived', role: 'superadmin')
user.validate # false
user.errors.full_messages # ["Role 'superadmin' is not a valid role", "Status 'archived' is not a valid status"]
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/OrestF/readymade. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Lead project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.