Last active
September 22, 2020 01:01
-
-
Save ahmad19/1fb643149079a19b4486ca8be42e5791 to your computer and use it in GitHub Desktop.
Validate and save json data into database using dry-rb gems and without using active record.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CreateIndividualDetails < ActiveRecord::Migration[6.0] | |
def change | |
create_table :individual_details do |t| | |
t.string :detail_type | |
t.json :individual_data | |
t.integer :user_id | |
t.timestamps | |
end | |
add_index :individual_details, :user_id, unique: true | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= form_tag('/post_url', method: 'post', id: 'details_form') do %> | |
<% if @form_tag.errors.present? %> | |
<div id="errors" class="panel panel-danger"> | |
<div class="panel-body"> | |
<ul> | |
<% @form_obj.errors.each do |key, msg| %> | |
<% msg.each do |m| %> | |
<li><%= key.to_s.humanize %> - <%= m %></li> | |
<% end %> | |
<% end %> | |
</ul> | |
</div> | |
</div> | |
<% end %> | |
<div> | |
<%= label_tag "individual[full_name]", "First Name" %> | |
<%= text_field_tag "individual[full_name]", @form_obj.full_name %> | |
</div> | |
<div> | |
<%= label_tag "individual[email]", 'Email' %> | |
<%= text_field_tag "individual[email]", @form_obj.email %> | |
</div> | |
<div> | |
<%= label_tag "individual[postal_code]", 'Zip Code' %> | |
<%= text_field_tag "individual[postal_code]", @form_obj.postal_code %> | |
</div> | |
<div> | |
<%= label_tag "individual[date_of_birth]", 'Date of Birth' %> | |
<%= date_field_tag "individual[date_of_birth]", @form_obj.date_of_birth %> | |
</div> | |
<%= submit_tag 'Submit Details', class: 'btn btn-primary' %> | |
<% end %> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
source 'https://rubygems.org' | |
ruby '2.6.5' | |
gem 'dry-initializer' | |
gem 'dry-struct' | |
gem 'dry-types' | |
gem 'dry-validation' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class IndividualController < ApplicationController | |
def new | |
@form_obj = IndividualForm.new | |
end | |
def create | |
@form_obj = IndividualForm.new( | |
onboarding_params.to_h.deep_symbolize_keys | |
) | |
if @form_obj.save(current_user.id) | |
redirect_to root_path, alert: 'Onboarding data has been successfully saved.' | |
else | |
render :new | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Payments | |
class IndividualForm < Dry::Struct | |
attr_accessor :record, :errors | |
attribute :full_name, Types::String.optional.default(nil) | |
attribute :email, Types::String.optional.default(nil) | |
attribute :postal_code, Types::String.optional.default(nil) | |
attribute :date_of_birth, Types::String.optional.default(nil) | |
def save(user_id) | |
attributes = to_h | |
@record = Payments::IndividualValidation.new.call(attributes) | |
@errors = @record.errors.to_h | |
return false unless @errors.empty? | |
save_response(@record.to_h, user_id) | |
end | |
private | |
def save_response(obj, user_id) | |
record = IndividualDetail.new | |
record.user_id = user_id | |
record.individual_data = obj | |
record.save! | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Payments | |
class IndividualValidation < Dry::Validation::Contract | |
MINIMUM_AGE = 18 | |
DIGITS_LENGTH = 6 | |
required(:full_name).filled(:string) | |
required(:email).filled(:string) | |
required(:postal_code).filled(:integer) | |
required(:date_of_birth).filled(:date) | |
rule(:postal_code) do | |
key.failure(I18n.t('individual.postal_code.length')) if key? && value.digits.count < DIGITS_LENGTH | |
end | |
rule(:date_of_birth) do | |
if (value < Date.today) && ((Date.today - value).to_i / 365) <= MINIMUM_AGE | |
key.failure(I18n.t('individual.date_of_birth.minor')) | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
this is a good use of struct and validation 👍
The only I would suggest here is to separate data (
Payments::IndividualForm
) and runtime logic (its#save
etc methods). How a bit of data is saved or validated isn't really a responsibility of that data, and it makes reusing that logic harder, while also coupling data structure and operations.For example, here I would leave
Payments::IndividualForm
is just a data struct with no methods attached to it, and extract validation and saving into their own command-like classes - this will make them reusable with other data structures that you might have in the future.This can also be paired nicely with using rom-rb instead of ActiveRecord, if you want to go all-in on new concepts :)