Skip to content

Instantly share code, notes, and snippets.

@ahmad19
Last active September 22, 2020 01:01
Show Gist options
  • Save ahmad19/1fb643149079a19b4486ca8be42e5791 to your computer and use it in GitHub Desktop.
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.
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
<%= 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 %>
source 'https://rubygems.org'
ruby '2.6.5'
gem 'dry-initializer'
gem 'dry-struct'
gem 'dry-types'
gem 'dry-validation'
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
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
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
@morhekil
Copy link

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment