ruby-on-railstrailblazer

Trailblazor operation -> collection :attribute(has_many through association) with populator -> How to populate the Reform::Form form array of slugs?


I'm using gem trailblazer.

I have following structure.

Following two models

class Company
  has_many :acquirable_source_companies,
           class_name: 'AcquirableSourceCompany',
           foreign_key: 'acquirer_company_id',
           inverse_of: :acquirer_company

  has_many :acquires_from_companies, through: :acquirable_source_companies, source: :acquires_from_company
end


class AcquirableSourceCompany
  belongs_to :acquirer_company,
             class_name: 'Company',
             foreign_key: 'acquirer_company_id',
             inverse_of: :acquires_from_companies

  belongs_to :acquires_from_company, class_name: 'Company', foreign_key: 'source_company_id'
end

Now I just want to use multi select to select acquirable companies when I edit parent companies.

In the frontend I'm having following

f.input :acquires_from_companies,
                as: :select,
                collection: ::Company.where.not(type: :parent).pluck(:slug),
                include_blank: true,
                selected: @company.acquires_from_companies.pluck(:slug),
                input_html: { multiple: true, class: 'select2' }

Now when I save this form, it passes the array of string in following format.

Parameters: {"authenticity_token"=>"<token>", "company"=>{"acquires_from_companies"=>["", "child_1", "child_2", "child_3"], ...}, id: 1 }

the operation class looks like following

module Companies
  module Operations
    class Update < ParentClass
      include Model
      model ::Company

      contract do
        collection :acquires_from_companies
      end
    end
  end
end

And the collection have array of string.

What is best way to convert it to Reform::Form or ActiveRecord collection.

before the parameter get validated.

I've tried following

contract do
  collection :acquires_from_companies, populator: :companies

  def companies(fragment:, **)
    acquires_from_companies = acquires_from_companies.push(Company.find_by(slug: fragment))
  end
end

But that empty string is not working out.

Also if I somehow manage to get rid of empty string from array. Should I build company object one by one or all together.

for any option either one by one or all together what should be the ideal implementation?

Also please let me know if more details are required to understand the question.


Solution

  • First as you mentioned the array of slugs is in acquires_from_companies so you can do is use the whole argument options instead of on key fragment in the argument.

    There will be a :doc present in the options, and that have all the parameters.

    Something like the following with two options.

    1. change the model
    contract do
      collection :acquires_from_companies, populator: :companies
    
      def companies(options)
        model.acquires_from_companies = Company.where(slug: options[:doc]['acquires_from_companies'])
      end
    end
    
    options[:doc]['acquires_from_companies'] 
    
    1. change the contract
    contract do
      collection :acquires_from_companies, populator: :companies
    
      def companies(options)
        self.acquires_from_companies = Company.where(slug: options[:doc]['acquires_from_companies'])
      end
    end
    

    These could be normal flow of steps in a trailblazer operation.

    1. Get the model object
    2. Build nested form tree
    3. Update form tree from params
    4. Run validation
    5. Assign the attributes to the model

    The difference between model and self in the above two examples is…

    When you choose model.attribute

    You directly change the 5 steps so validation might fail because of not having the right value in the params. Because what you have updated is model attribute not params.

    When you choose the self.attribute You change at the 2nd step. You go with flow, and all further steps will have updated value in the params.

    I would suggest changing the contract/params instead of the model.

    But just for sake of sharing I’m sharing the option of model as well.

    In any case, model updates should be avoided in the populator. But you just don’t have validation and don’t care about in between steps, you can go with the model update option.

    Trailblazer is an awesome gem, you won’t like normal structure after that.