Search code examples
ruby-on-railsruby-on-rails-5nested-formscocoon-gem

How to set foreign key using value from nested form?


I have a Rails app that displays Content. Each piece of Content belong_to a Source and a Source has_many Contents. Each Source consists of a name and a domain.

The Content also belongs_to an Edition. The way my app is set up is, in the form to create/edit Editions, I have nested the form fields for Contents, using the Cocoon gem.

The nested fields for contents include a link field. What I need to do is check the link against the various domains in the Sources table and set the relevant source_id on the newly created/edited content.

I was thinking that I could set the relevant source_id in the editions controller on the update or create actions. However, since the only data I receive is a params hash with an embedded contents_attributes hash (which holds no reference to the source_id, since the source is not set in the form), how can I set the source_id using the 'link' submitted on the form?

Here's my create and update actions on the editions_controller:

def create
  @edition = Edition.new(edition_params)

  respond_to do |format|
    if @edition.save
      format.html { redirect_to @edition, notice: 'Edition was successfully created.' }
      format.json { render :show, status: :created, location: @edition }
    else
      format.html { render :new }
      format.json { render json: @edition.errors, status: :unprocessable_entity }
    end
  end
end

def update
  respond_to do |format|
    if @edition.update(edition_params)
      format.html { redirect_to @edition, notice: 'Edition was successfully updated.' }
      format.json { render :show, status: :ok, location: @edition }
    else
      format.html { render :edit }
      format.json { render json: @edition.errors, status: :unprocessable_entity }
    end
  end
end

And here are the params used:

def edition_params
  params.require(:edition).permit(:date, 
                                  :clicks, 
                                  :product_id,
                                  contents_attributes: [:id,
                                                        :heading,
                                                        :body,
                                                        :link,
                                                        :top_story,
                                                        :section_id,
                                                        :_destroy
                                                       ]
                                 )
end

Should I have a hidden input on the form with the source_id? Or can this be done as is on the controller?


Solution

  • If you have the link value set in contents form AND you have a logic to determine the source from the value of link, then you don't need to set source_id in form OR in the controller.

    Best place to set the source_id would be in the model, so that it is always set no matter how you create the content record i.e. from editions form, from console, or from some other controller. You won't have to worry about that in this case.

    Having these associations and callbacks in your models should solve your purpose:

    class Source
      # field :name
      # field :domain
    
      has_many :contents
    
      def self.fetch_from_link(link)
        match_link_with_domains_and_return_source_record
      end
    end
    
    class Edition
      # field :date
      # field :clicks
    
      has_many :contents
      accepts_nested_attributes_for :contents
    end
    
    class Content
      # field :heading
      # field :body
      # field :link
      # field :top_story
    
      belongs_to :source
      belongs_to :edition
    
      before_validation :set_source
    
      private
    
      def set_source
        # Set source *only* if not already set. You can change `||=` to `=` to set it always.
        self.source ||= link && Source.fetch_from_link(link)
      end
    end