Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save dynamic fields in Rails

In my Rails app I Pages, Blocks, BlockContents, Fields, and FieldContents.

The associations are:

class Page < ActiveRecord::Base
  has_many :blocks
  has_many :block_contents, :through => :blocks
end

class Block < ActiveRecord::Base
  belongs_to :page
  has_many :block_contents
end

class BlockContent < ActiveRecord::Base
  belongs_to :block
  has_many :field_contents
end

class Field < ActiveRecord::Base
  has_many :field_contents
end

class FieldContent < ActiveRecord::Base
  belongs_to :block_content
  belongs_to :field
  validates :content, presence: true
end

Using this I am able to have dynamic fields for Page.

However writing to the DB is currently done in the controller like so:

def update
  @page = Page.find(params[:id])
  if params[:field_content].present?
    params[:field_content].each do |block_content|
      block_content.last.each do |field|
        field_content_row = FieldContent.where(block_content_id: block_content.first, field_id: field.first).first
        if field_content_row.present?
          field_content_row.update(content: field.last)
        else
          field_content = FieldContent.new
          field_content.block_content_id = block_content.first
          field_content.field_id = field.first
          field_content.content = field.last
          field_content.save
        end
      end
    end
  end
  if @page.update_attributes(page_params)
    redirect_to pages_path
  else
    render 'edit'
  end
end

While this does in work, it's quite dirty and it doesn't show my validations for my FieldContent in the view (the validations do work as they don't save the FieldContent if they are invalid, but it should prevent the whole page from saving and show it in the view).

How can I make it so that I can show the validations for FieldContent?


For those that want to see the view:

<% @page.blocks.each do |block| %>
    <% block.block_contents.each do |block_content| %>
        <% field_group.fields.each do |field| %>
            <input name="field_content[<%= block_content.id %>][<%= field.id %>]" id="field_content_<%= block_content.id %>_<%= field.id %>" type="text" value="<%= get_field_content(block_content, field.name) %>">
        <% end %>
    <% end %>
<% end %>

and the helper I use to get the content:

def get_field_content(block_content, field)
  content = ''
  block_content.field_contents.each do |field_content|
    if field_content.field.name == field && field_content.block_content_id == block_content.id
      content = field_content.content
    end
  end
  content
end
like image 514
Cameron Avatar asked Jan 05 '17 15:01

Cameron


1 Answers

Firstly what you need to do firstly in order to submit a form containing multiple models is to accept nested attributes.

In your page.rb file, put:

class Page < ActiveRecord::Base
has_many :blocks
has_many :block_contents, :through => :blocks
accepts_nested_attributes_for :field_contents

Secondly in the view - You must specify which fields are for which model using 'fields_for' So inside the form_for @page, you should put:

 <%= f.fields_for :field_contents do |b| %>

Since :field_contents is the only object that will be updated along with @page. So that the form element in it's entirety looks like this:

<% @page.blocks.each do |block| %>
    <% block.block_contents.each do |block_content| %>
        <% field_group.fields.each do |field| %>
            <%= form_for @page do |f| %>
                <%= f.input :any attribute %>
                    <%= f.fields_for :field_contents do |b| %>
                        <%= b.text_field :content %>
                            <%= f.fields_for :block_contents do |b| %>
                               <%= b.hidden_field :block_content, value: "#{@block_content.id}"%>
                    <% end %>
                <% end %>
            <% end %>
        <% end %>
    <% end %>
<% end %>

Since I've learnt you can already access @page.field_content, you don't have to touch anything in the controller as it updates the record automatically based on the existing relation.

So now that we have all models being submitted in a single request, you specified that you want error messages to show inside the view after validation has taken place. And this is the easy part.

From what I can see, you're currently validating the presence of :content.

In your view: simply put a HTML tag or a rails content tag

<p class= "error"><%= "Your error message"if @field_content.errors[:content].present? %></p>

Let me know if you have any further questions.

like image 146
Crashtor Avatar answered Nov 03 '22 20:11

Crashtor