New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

rfo

Package Overview
Dependencies
Maintainers
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rfo

  • 0.0.1
  • Rubygems
  • Socket score

Version published
Maintainers
2
Created
Source

Code Climate

RFO - Rails Form Object

Inspired by [7 Patterns to Refactor Fat ActiveRecord Models] (http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/) and SRP

What it does?

  • simplify forms with multiple models - we do not have to use nested attributes
  • remove validations from models - they do too much already. What is more each form can have different validations.
  • can substitude strong parameters - we define exacly what and how we want to assign values in models

Installation

Add this line to your application's Gemfile:

gem 'rfo', github: 'petergebala/rfo'

And then execute:

$ bundle

Or install it yourself as:

$ gem install rfo # Not yet!

Usage

It works great with Draper and Simple Form

Define simple model with relations eventually callbacks:

class Organisation < ActiveRecord::Base
  WEBSITE_REGEXP = /[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?/ix
  APPLY_FOR = [:under_2000, :over_2000]

  belongs_to :grant, touch: true
  has_one :address, as: :entity, dependent: :destroy
  has_one :contact, as: :entity, dependent: :destroy
  has_one :user, through: :grant

  after_create :create_address
  after_create :create_contact

  delegate :postcode, to: :address
end

Define form:

  • fields which you will use in form
  • validations for this form
  • default values
  • how and where it should assign values
# app/forms/organisation_form.rb
class OrganisationForm < RFO::Base
  # About your organisation
  attribute :amount_apply_for,  String
  attribute :organisation_name, String
  attribute :charity_number,    String

  # Your address details
  attribute :first_name,        String
  attribute :last_name,         String
  attribute :position,          String
  attribute :address_line_1,    String
  attribute :address_line_2,    String
  attribute :address_line_3,    String
  attribute :town,              String
  attribute :county,            String
  attribute :postcode,          String

  # Your contact details
  attribute :phone_number,      String
  attribute :mobile_number,     String

  # Your organisation
  attribute :website,           String
  attribute :description,       String

  # Validations
  validates :description,       presence: true, length: { maximum: 2000 }
  validates :amount_apply_for,  presence: true, inclusion: { in: Organisation::APPLY_FOR.map(&:to_s) }
  validates :organisation_name, presence: true, length: { maximum: 255 }
  validates :first_name,        presence: true, length: { maximum: 255 }
  validates :last_name,         presence: true, length: { maximum: 255 }
  validates :address_line_1,    presence: true, length: { maximum: 255 }
  validates :town,              presence: true, length: { maximum: 255 }
  validates :postcode,          presence: true, length: { maximum: 10 },  format: { with: Address::POSTCODE_REGEXP }
  validates :phone_number,      presence: true, length: { maximum: 20 },  format: { with: Contact::PHONE_NUMBER_REGEXP }
  validates :mobile_number,                     length: { maximum: 20 },  format: { with: Contact::PHONE_NUMBER_REGEXP }, allow_blank: true
  validates :website,                           length: { maximum: 255 }, format: { with: Organisation::WEBSITE_REGEXP }, allow_blank: true

  private
  def assign_defaults
    @grant.current_step = :organisation_details

    organisation_contact = @organisation.contact
    organisation_address = @organisation.address
    user_contact         = @organisation.user.contact
    user_address         = @organisation.user.address

    # Initialize with values from diffrent models
    self.organisation_name ||= @organisation.name
    self.first_name        ||= organisation_contact.first_name     || user_contact.first_name
    self.last_name         ||= organisation_contact.last_name      || user_contact.last_name
    self.position          ||= organisation_contact.position       || user_contact.position
    self.phone_number      ||= organisation_contact.phone_number   || user_contact.phone_number
    self.mobile_number     ||= organisation_contact.mobile_number  || user_contact.mobile_number
    self.address_line_1    ||= organisation_address.address_line_1 || user_address.address_line_1
    self.address_line_2    ||= organisation_address.address_line_2 || user_address.address_line_2
    self.address_line_3    ||= organisation_address.address_line_3 || user_address.address_line_3
    self.town              ||= organisation_address.town           || user_address.town
    self.county            ||= organisation_address.county         || user_address.county
    self.postcode          ||= organisation_address.postcode       || user_address.postcode
  end

  def persist!
    ActiveRecord::Base.transaction do |t|
      @organisation.name             = self.organisation_name
      @organisation.amount_apply_for = self.amount_apply_for
      @organisation.charity_number   = self.charity_number
      @organisation.website          = self.website
      @organisation.description      = self.description

      @contact               = @organisation.contact
      @contact.first_name    = self.first_name
      @contact.last_name     = self.last_name
      @contact.position      = self.position
      @contact.phone_number  = self.phone_number
      @contact.mobile_number = self.mobile_number

      @address                = @organisation.address
      @address.address_line_1 = self.address_line_1
      @address.address_line_2 = self.address_line_2
      @address.address_line_3 = self.address_line_3
      @address.town           = self.town
      @address.county         = self.county
      @address.postcode       = self.postcode

      @grant.current_step = @grant.next_step

      @organisation.save!
      @contact.save!
      @address.save!
      @grant.save!
    end
  end
end

Define skinny controller:

class OrganisationDetailsController < ApplicationController
  before_filter :set_grant
  respond_to :html

  before_filter :set_organisation, only: [:edit, :update]

  def edit ; end

  def update
    flash[:notice] = 'Organisation details saved!' if @organisation_form.update_attributes(organisations_params)
    respond_with @organisation_form, location: @grant.current_path
  end

  private
  def set_grant
    @grant = current_user.grants.find(params[:grant_id]).decorate
  end

  def set_organisation
    @organisation = @grant.organisation.decorate
    @organisation_form = OrganisationForm.new(organisation: @organisation,
                                              grant: @grant)
  end

  def organisations_params
    params.require(:organisation_form).permit(:amount_apply_for,
                                              :organisation_name,
                                              :charity_number,
                                              :first_name,
                                              :last_name,
                                              :position,
                                              :address_line_1,
                                              :address_line_2,
                                              :address_line_3,
                                              :town,
                                              :county,
                                              :postcode,
                                              :phone_number,
                                              :mobile_number,
                                              :website,
                                              :description)
  end
end

And clean view!

= simple_form_for @organisation_form, url: grant_organisation_details_path(@grant), method: :patch do |f|
  = f.error_notification
  h2 About Your Organisation
  fieldset
    = f.input :amount_apply_for,
      collection: @organisation.amount_apply_for_buttons_for_views,
      as: :radio_buttons,
      wrapper: :bootstrap_group_horizontal
    = f.input :organisation_name
    = f.input :charity_number
  h2 Your Address Details
  fieldset
    = f.input :first_name
    = f.input :last_name
    = f.input :position
    = f.input :address_line_1
    = f.input :address_line_2
    = f.input :address_line_3
    = f.input :town
    = f.input :county
    = f.input :postcode
  h2 Your Contact Details
  fieldset
    = f.input :phone_number
    = f.input :mobile_number
  h2 Your Organisation
  fieldset
    = f.input :website, as: :addon, input_html: { addon_text: 'http://' }
    = f.input :description, as: :text
  h2 Proceed
  fieldset
    = f.button :submit

Additional information

Becasue Form Object is just plain ruby class you can:

  • test it in simple way,
  • share common code (like validations) between form objects,
  • inherit between forms.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

TODO:

  • turn off strong_parameters
  • write tests
  • correct documentation
  • update documentation with has_many relation and show how to remove nested_attributes

FAQs

Package last updated on 17 Jul 2014

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc