Building anything is easy if one knows the basic rules. Generally it’s very good to use conventions in everything you do, this gives other people a chance to understand your work and maybe participate in finding a better solution to problem. However, sometimes you choose to walk a diffferent path, and instead of building an application in the most popular way you decide to find and use something still unpopular. This happened to me last time, in a very simple situation. I just wanted a form that will simultaneously create many objects. I know there are popular solutions likenested forms but I made it different.

Trailblazer

Before showing you my solution it is recommended to understand how it is going to work, and what are the basic principals. I used a gem which is a part of a bigger project, a new architecture for Rails apllications, called Trailblazer. As it stays on the github page:

"Trailblazer is a thin layer on top of Rails. It gently enforces encapsulation, an intuitive code structure and gives you an object-oriented architecture."

The creators of this gem are trying to change the architecture of rails applications. As a result your models will not contain any logic inside, no callbacks, nested attributes or even validations. Controllers will be less extensive and helpers will be replaced by an additional abstract layer. It is a new structure of your application, with the code organized in smaller components created to handle different concerns. Framework is concept-driven, which means that all files connected with one concept (models, views, assets etc.) are stored in one place. It adds new layers to MVC structure of rails application. It separates codes into: controller, operation and cells. Operation contains model, form object and representer object. Cell object is rendering a view. On their Github page you can find a link to a sample application, which will help you its structure. I think this is really worth checking, even if you are not going to use it even once in your life. You will see that there is also a second path, a different way of thinking, with its pros and cons and sometimes it may prove to be a better solution for your particular app requirements.

Reform

It would be really difficult to implement this kind of solution, especially if development has already started. Fortunately it can be implemented partially in the typical, conventional rails application. And this is the approach I have used in my situation. Instead of changing the entire architecture of the app I added a reform gem which allows me to create a form model. It can handle validations and wrap selected models in one form, so exactly what I needed.

The part of my application’s structure defining ‘product’ looks like this:

├── models
│   ├── product
│   │   ├── description.rb
│   │   ├── image.rb
│   │   ├── product.rb

I have three models inside the product module. Both description.rb and image.rb contain fields defining their respective models, and both are related to product.rb. To generate a form that will create those three objects at once I will use reform gem.

 gem 'reform'

Inside the product module I added a new class – ProductForm.rb which will define form fields using::property, and all validations.

odule Product
  class ProductForm < Reform::Form
    property :name
    validates :name, presence: true

    property :description do
      property :content
      property :short_content
      validates :content, :short_content, presence: true
    end

    collection :images do
      property :image_path
      property :image_title
      validates :image_title, :image_path, presence: true
    end
  end
end

ProductForm is a class that inherits from Reform gem. A property defines field of the product object to be created, additionally I added validations. Product is in a 1-1 relation with the description model (has_one), so it can be treated as another ::property, but as a block. Inside are fields of this particular model, with validations. Product is also in relation with image model (has_many), so it is added in the same way as description, but instead of property it is a collection. It works like a nested property that iterates over objects collection.

class ProductsController < ApplicationController
  before_action :new_form_setup, only: [:new, :create]

  def new ; end

  def create
    if @form.validate(params[:product_product])
      @form.save
      redirect_to admin_dashboard_path, notice: t('admin.product.create_success')
    else
      render 'new', alert: @form.errors.full_messages.join(', ')
    end
  end

  private

  def product_params
    params.require(:product).permit(:id, :name)
  end

  def new_form_setup
    @product = Product::Product.new
    @product.description = Product::Description.new
    @product.images.build
    @form = Product::ProductForm.new(@product)
  end
end

To render a form all objects have to be initialized on both new and create method as shown in the new_form_setup. It is also important to understand how create action works. Form object is saved instead of product, and it depends on params validations. Instead of this detail both actions look pretty similar to typical ones.

= form_for @form, url: products_path, method: :post do |f|
  = t('admin.dashboard.product.name')
  = f.text_field :name
  = f.fields_for :description do |d|
    .form-group
      = t('admin.dashboard.product.content')
      = d.text_field :content
    .form-group
      = t('admin.dashboard.product.short_content')
      = d.text_field :short_content
  = f.fields_for :images do |i|
    .form-group
      = t('admin.dashboard.product.image_title')
      = i.text_field :image_title
    .form-group
      = t('admin.dashboard.product.image_path')
      = i.file_field :image_path
  .form-group
    = f.submit t('admin.dashboard.product.submit'), name: nil, class: 'btn btn-primary'

Form created in haml with translation looks more or less like this. Again it is not working on the @product but on the @form. Thats the only difference between conventional forms.

As you see the gem gives a new functionality without any difficult changes in the code (but with a change in structure and meaning). This is just simple usage, the gem is far more powerful and gives oportunities to use it in many cases. It is really worth to check out. This solution can be even used as a first step to trying Trailblazer framework itself. I think it is sometimes very good to try a different way of building applications.

Post tags:

Work
with us

Tell us about your idea
and we will find a way
to make it happen.

Get estimate

Join our awesome team
Check offers