简体   繁体   中英

Creating a new object in form_for for Ajaxified form in Rails 4

Using Rails 4, making a simple mostly single-page app.

My root is set to the index page where I display a series of very short posts. On the side, I have a form partial so someone can easily submit a new post.

I decided to Ajaxify the form submission so that the new post is appended to the top of the list without reloading the page and the form is cleared.

One note about my form - Every post belongs to a company, and so I have <%= fields_for Company.new do |company|... %> inside the form. In the create action, I call first_or_create on the company, so if the company doesn't exist, it is created and related to the new post upon submission.

Ultimately, I had to explicitly create new objects in my form as opposed to passing in locals to my partials and using bare words in the form. I'm trying to understand why this is and how I can ultimately implement a more accepted approach.

Before implementing my ultimate solution below, I was trying to get it to work like this: in my index action in the posts controller, declaring not only @post = Post.new and @company = Company.new but also declaring @new_company = Company.new and @new_post = Post.new . Then in my create.js.erb file, when re-rendering the form, passing in @new_company and @new_form as locals like $(".js-ref-form").html("<%= escape_javascript(render partial: 'form', locals: { post: @new_post, company: @new_company ) %>"); and then in the form, using bare words <%= form_for post .... %> . That seemed like the more standard way, but I couldn't get it to work until I abandoned instance variables and locals and just explicitly created the new object in the form.

posts_controller.rb:

class PostsController < ApplicationController

  respond_to :html, :js

  def index
    @company = Company.new
    @post = Post.new
    @posts = Post.all
  end

  def create
    @company = Company.where(name: post_params[:company]).first_or_create
    @post = @company.posts.build(company_id: @company.id, details: post_params[:details], link: post_params[:link],
                                         expiration: post_params[:expiration], code: post_params[:code], limit: post_params[:limit])
    if @post.save
      flash[:success] = "Post submitted!"
    else
      flash[:danger] = "Problem submitting post. Please try again."
    end

    respond_with(@post) do |f|
      f.html { redirect_to root_path }
    end
  end

views/posts/create.js.erb:

<% if @post.valid? %>
  $(".js-post").prepend("<%= escape_javascript(render(@post)) %>");
  $(".js-post-form").html("<%= escape_javascript(render partial: 'form') %>");
<% else %>
  $(".js-post-form").replaceWith("<%= escape_javascript(render partial: 'form') %>");
<% end %>

views/posts/_form.html.erb

# writing form_for @post doesn't work with this solution, and if I pass
# in locals to the partials, i.e. render partial: 'form', locals: { post: @post }
# and using form_for post didn't work either
<%= form_for Post.new, html: { class: 'form-horizontal' }, remote: true do |f| %>
  <h2>Add Post</h2>
  <%= render 'shared/error_messages' %>

  <%= f.label :company, "Company" %>
  # Same problem as the comment above
  <%= fields_for Company.new do |company| %>
      <%= f.text_field :company, class: 'form-control' %>
  <% end %>

  <br>

  <%= f.label :details, "What's new?" %> <span class="detail-counter pull-right"></span>
  <%= f.text_area :details, class: 'form-control' %>

  <br>

  <%= f.submit "Submit", class: 'btn btn-lg btn-block btn-primary' %>
<% end %>

<script type="text/javascript">
  var elem = $(".detail-counter");
  $("#post_details").limiter(140, elem);
</script>

views/posts/index.html.erb:

<div class="row">
  <div class="col-md-4 post-form">
    <div class="js-post-form">
      <%= render partial: 'form' %>
    </div>
  </div>
  <div class="col-md-8">
    <div class="post-list js-posts">
      <% if @posts.any? %>
        <%= render @posts %>
      <% else %>
        No posts yet!
      <% end %>
    </div>
  </div>
</div>

routes.rb

CompanyPosts::Application.routes.draw do
  resources :posts

  root to: 'posts#index'
end

MODELS:

post.rb

class Post < ActiveRecord::Base
  default_scope -> { order('created_at DESC') }
  validates :details, presence: true, length: { maximum: 140 }

  belongs_to :company

company.rb

class Company < ActiveRecord::Base
  has_many :posts, dependent: :destroy
end

Ok, figured it out. To be able to pass in locals to my form partial and use bare words in the form, I had to create instance variables for a new post and new company objects in the posts#create action, rather than in the index action as I was doing before.

Then in my index.html.erb file I can do <%= render partial: 'form', locals: { post: @post, company: @company } %> and in create.js.erb:

<% if @post.valid? %>
  $(".js-posts").prepend("<%= escape_javascript(render(@post)) %>");
  $(".js-post-form").html("<%= escape_javascript(render partial: 'form', locals: { post: @new_post, company: @new_company }) %>");
<% else %>
  $(".js-post-form").html("<%= escape_javascript(render partial: 'form', locals: { post: @post, company: @company }) %>");
<% end %>

Also, I had to define two parameters in my posts controller because I'm effectively submitting two forms at the same time. I modified the fields_for tag to work in what I hope is a more appropriate way:

<%= form_for post, html: { class: 'form-horizontal' }, remote: true do |f| %>
  <h2>Add Post</h2>
  <%= render 'shared/error_messages' %>

  <%= f.label company, "Company" %>
  <%= fields_for company do |company| %>
      <%= company.text_field :name, class: 'form-control' %>
  <% end %>
  # The rest of the form is the same as above...

class PostsController < ApplicationController

  respond_to :html, :js

  def index
    @company = Company.new
    @post = Post.new
    @posts = Post.all
  end

  def create
    @company = Company.where(name: company_params[:name]).first_or_create
    @post = @company.posts.build(post_params)
    @new_post = Post.new
    @new_company = Company.new

    if @post.save
      flash[:success] = "Post submitted!"
    else
      flash[:danger] = "Problem submitting post. Please try again."
    end

    respond_with(@post) do |f|
      f.html { render :index }
    end
  end

  private

    def post_params
      params.require(:post).permit(:details)
    end

    def company_params
      params.require(:company).permit(:name)
    end
end

And now I don't have to explicitly create the objects inside my form. Though any further suggestions for improvement are still appreciated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM