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.