Rails 5, Cocoon Gem - nested form inside nested form - javascript

I am trying to use cocoon gem to build nested forms.
I have models for Organisation, Package::Bip and Tenor.
The associations are:
Organisation
has_many :bips, as: :ipable, class_name: Package::Bip
accepts_nested_attributes_for :bips, reject_if: :all_blank, allow_destroy: true
Package::Bip (polymorphic)
belongs_to :ipable, :polymorphic => true, optional: true, inverse_of: :bip
has_one :tenor, as: :tenor
accepts_nested_attributes_for :tenor, reject_if: :all_blank, allow_destroy: true
Tenor (polymorphic)
belongs_to :tenorable, :polymorphic => true, optional: true
The forms have:
In my organisations/_form.html.erb, I have:
<%= f.simple_fields_for :bips do |f| %>
<%= f.error_notification %>
<%= render 'package/bips/bip_fields', f: f %>
<% end %>
<%= link_to_add_association 'Add another intellectual property resource', f, :bips, partial: 'package/bips/bip_fields' %>
In my bip_fields.html.erb nested form, I have:
<%# if #package_bips.tenor.blank? %>
<%= link_to_add_association 'Add timing', f, :tenor, partial: 'tenors/tenor_fields' %>
<%# end %>
<%= f.simple_fields_for :tenor do |tenor_form| %>
<%= f.error_notification %>
<%= render 'tenors/tenor_fields', f: tenor_form %>
<% end %>
Javascript
The cocoon docs suggest adding a js file to specify association-insertion-node as a function. In my tenor_subform.js I have:
$(document).ready(function() {
$(".add_tenor a").
data("association-insertion-method", 'append').
data("association-insertion-node", function(link){
return link.closest('.row').next('.row').find('.tenor_form')
});
});
Controllers
In my organisation controller, I have:
def new
#organisation = Organisation.new
#organisation.bips
end
Note: I'm not sure if I need to add another line to my new action to create the organisation.bip.tenor instance. I'm also unsure if im supposed to add has_one through association on the organisation.rb that references the tenor.
def organisation_params
params.fetch(:organisation, {}).permit(:title, :comment,
bips_attributes: [:id, :status, :_destroy,
tenor_attributes: [:id,:commencement, :expiry, :_destroy]
],
In my tenor controller, I have:
def tenor_params
params.require(:tenor).permit( :commencement, :expiry)
end
ERRORS
I am not sure if I need to add tenor actions to the organisation controller (the ultimate parent of bip which in turn is the parent of tenor).
When I save all of this and try it, I get an error that says:
unknown attribute 'tenor_id' for Tenor.
When I see other SO posts with this error, its often because the :id attribute hasn't been whitelisted in the parent class. I have done that.
Can anyone see what I've done wrong?
Tenor controller
class TenorsController < ApplicationController
before_action :set_tenor, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
# after_action :verify_authorized
def index
#tenors = Tenor.all
# authorize #tenors
end
def show
end
def new
#tenor = Tenor.new
# authorize #tenor
end
def edit
end
def create
#tenor = Tenor.new(tenor_params)
# authorize #tenor
respond_to do |format|
if #tenor.save
format.html { redirect_to #tenor }
format.json { render :show, status: :created, location: #tenor }
else
format.html { render :new }
format.json { render json: #tenor.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #tenor.update(tenor_params)
format.html { redirect_to #tenor }
format.json { render :show, status: :ok, location: #tenor }
else
format.html { render :edit }
format.json { render json: #tenor.errors, status: :unprocessable_entity }
end
end
end
def destroy
#tenor.destroy
respond_to do |format|
format.html { redirect_to action: :index }
format.json { head :no_content }
end
end
private
def set_tenor
#tenor = Tenor.find(params[:id])
# authorize #tenor
end
def tenor_params
params.require(:tenor).permit(:express_interest, :commencement, :expiry, :enduring, :repeat, :frequency)
end
end

You has_one relation is wrongly declared. Because you say as: :tenor makes it look for a tenor_id.
You have to declare it as follows:
has_one :tenor, as: :tenorable

Your model does not see the id of nested_attr.Add :inverse_of => #{model}.
Example:
class Tenor < ActiveRecord::Base
has_many :traps, :inverse_of => :bips
end
For more information look this or this documentation.

Related

Validation errors not showing after I ajax a rails form

I am using ajax-rails to render my form and validations stopped working. When I click to submit empty for which should validate, validation does not take effect and backend log shows it posted empty form. If I don't use ajax it works fine. I don't know what I am missing.
model
class ClockEntry < ApplicationRecord
belongs_to :user
validates :purpose, presence: true # I validated the attribute
end
index.html.erb
<div class="container" id="new-clock-entry">
<%= link_to 'New Clock Entry', new_clock_entry_path, remote: true, class: 'btn btn-primary btn-sm' %>
</div>
_form.html.erb
<%= simple_form_for clock_entry, remote: true do |f| %>
<%= f.error_notification %>
<%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
<div class="form-inputs">
<%= f.input :purpose %>
</div>
<div class="form-actions">
<%= f.button :submit, class: 'btn btn-primary btn-block btn-lg' %>
</div>
<% end %>
new.html.erb
<h1>New Clock Entry</h1>
<%= render 'form', clock_entry: #clock_entry %>
<%= link_to 'Back', clock_entries_path %>
new.js.erb
$('#new-clock-entry a').hide().parent().append("<%= j render 'form', clock_entry: #clock_entry %>")
create.js.erb
$('#new-clock-entry form').remove();
$('#new-clock-entry a').show();
$('table#clock-entry tbody').append("<%= j render #clock_entry %>");
controller
def new
#clock_entry = ClockEntry.new
end
def create
#clock_entry = current_user.clock_entries.new(clock_entry_params)
#clock_entry.set_time_in
respond_to do |format|
if #clock_entry.save
format.js { redirect_to root_path, notice: 'Clock entry was successfully created.' }
format.html { redirect_to #clock_entry, notice: 'Clock entry was successfully created.' }
format.json { render :show, status: :created, location: #clock_entry }
else
format.html { render :new }
format.json { render json: #clock_entry.errors, status: :unprocessable_entity }
end
end
end
#arieljuod in the comments section was very instrumental to me solving this. As he mentioned firstly, I am not asking my format to respond to js under the else condition of the create method. So this is what I did:
controller create action
Add the line below to the else condition of the create action:
format.js { render :new }
So my controller action becomes:
def create
#clock_entry = current_user.clock_entries.new(clock_entry_params)
#clock_entry.set_time_in
respond_to do |format|
if #clock_entry.save
format.html { redirect_to #clock_entry, notice: 'Clock entry was successfully created.' }
format.js { redirect_to root_path, notice: 'Clock entry was successfully created.' }
format.json { render :show, status: :created, location: #clock_entry }
else
format.js { render :new } # Added this...
format.html { render :new }
format.json { render json: #clock_entry.errors, status: :unprocessable_entity }
end
end
end
new.js.erb file
Then in the new.js.erb file, upon rendering the :new form, you need to remove or hide what is already there and append a new form that has the error message. So I had to remove the whole form by supplying the form tag to be hidden in my new.js.erb. So I add this line below to my new.js.erb file:
$('#new-clock-entry form').hide().parent().append("<%= j render 'form', clock_entry: #clock_entry %>")
So new new.js.erb file now becomes:
$('#new-clock-entry a').hide().parent().append("<%= j render 'form', clock_entry: #clock_entry %>")
$('#new-clock-entry form').hide().parent().append("<%= j render 'form', clock_entry: #clock_entry %>")
I think this should be handy to anyone who runs into the same problem.

No template found for SomeController#create rendering head :no_content

I am building a rails app that creates a like(create) or unlike(destroy), on a link click with a remote: true ajax request
The Post goes through and the transaction is committed but the the js response is not going through and I receive a Error in the server console
No template found for ForumThreads::ForumPosts::LikesController#create, head :no_content
the view structure is
forum_threds/show.html.haml
....
= render #forum_thread.forum_posts
....
to render a view of every post that is in the thread
than
forum_posts/_forum_post.html.haml
....
%div{id: "fourm_post_#{forum_post.id}" }
= render partial: "forum_posts/likes", locals: { post: forum_post }
....
which is rendering
forum_posts/_likes.html.haml
- if user_signed_in? && current_user.likes?(post)
= link_to "UnLike", forum_post_likes_path(post), method: :delete,
remote: true
- else
= link_to "Like", forum_post_likes_path(post), method: :post, remote:
true
and my controller is
class ForumThreads::ForumPosts::LikesController < ApplicationController
before_action :authenticate_user!
before_action :set_forum_post
before_action :set_forum_thread
def create
#forum_post.likes.where(user_id: current_user.id).first_or_create
respond_to do |format|
format.js
format.html { redirect_to #forum_thread}
end
end
def destroy
#forum_post.likes.where(user_id: current_user.id).destroy_all
respond_to do |format|
format.js
format.html { redirect_to #forum_thread}
end
end
private
def set_forum_thread
#forum_thread = ForumThread.find(#forum_post.forum_thread_id)
end
def set_forum_post
#forum_post = ForumPost.find(params[:forum_post_id])
end
end
With remote: false this works as expected with the page reload but when i put remote: true I receive the no template error
my file structure for the views
forum_threads/
show.html.haml
forum_posts/
show.html.haml
_likes.html.haml
likes/
create.js.erb
destroy.js.erb
and the create.js.erb and destroy.js.erb
$("#forum_post_<%= forum_post.id %>").html(<%= j render
'forum_posts/likes' %>);
but i know that this file isn't even being called
is there a way from the format.js that i may be able to call the file i need directly? or something?

undefined method `company' for nil:NilClass

I keep receving an error on my home page saying
'undefined method `company' for nil:NilClass'
for the first line of the following code displayed below. I am not really sure on how to fix this. I have two Controllers for both companies and customers because I am creating a two-sided marketplace.
When the user clicks the logo at the top instead of being redirected to the welcome page, the login page is being rendered since the home page is the root_path but I am using devise for before_action authenticate is being used.
<% if((current_user.company) || (current_user.customer)) %>
<%= render 'pages/welcome' %>
<% else %>
<% if current_user.is_company %>
<%= render 'companies/form', company: #company%>
<% else %>
Here is the home.html.erb file with the code which is the root_path
<% if((current_user.company) || (current_user.customer)) %>
<%= render 'pages/welcome' %>
<% else %>
<% if current_user.is_company %>
<%= render 'companies/form', company: #company%>
<% else %>
<%= render 'customers/form', customer: #customer%>
<% end %>
<% end %>
Here is my companiesController
class CompaniesController < ApplicationController
before_action :set_company, only: [:show, :edit, :update, :destroy]
def index
#companies = Company.all
end
# GET /companies/1
# GET /companies/1.json
def show
#company = Company.find(params[:company_id])
end
# GET /companies/new
def new
#company = Company.new
end
# GET /companies/1/edit
def edit
end
# POST /companies
# POST /companies.json
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
format.html { redirect_to #company, notice: 'Company was successfully created.' }
format.json { render :show, status: :created, location: #company }
else
format.html { render :new }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /companies/1
# PATCH/PUT /companies/1.json
def update
respond_to do |format|
if #company.update(company_params)
format.html { redirect_to #company, notice: 'Company was successfully updated.' }
format.json { render :show, status: :ok, location: #company }
else
format.html { render :edit }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
end
end
# DELETE /companies/1
# DELETE /companies/1.json
def destroy
#company.destroy
respond_to do |format|
format.html { redirect_to companies_url, notice: 'Company was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_company
#company = Company.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the
white list through.
def company_params
params.require(:company).permit(:username, :phone, :website, :street, :city, :state, :country, :user_id)
end
end
it just says that the current_user is nil, you need to make sure that the user is logged in to use current_user.company
The log is giving you details as to why the company is returning a nil. The company requires an object in it for the page to load properly. But that is not so because no user is logged in for the company to have an object to return.
<% if((current_user.company) || (current_user.customer)) %> <%=
render 'pages/welcome' %> <% else %>
<% if current_user.is_company %> <%= render 'companies/form',
company: #company%> <% else %>
is returning a nil because no user is logged in.
You can first check if there is a user logged in and then render
something before this to prevent the error page
<% if(current_user) <% if((current_user.company) ||
(current_user.customer)) %>
<%= render 'pages/welcome' %> <% else %>
<% if current_user.is_company %>
<%= render 'companies/form', company: #company%> <% end %> <% else %> render something here <% end %>
Or simply log in to make sure this does not return a nil. But the first fix is the most ideal.

Update nested form attribute with a iteration

I'm currently trying to use the nested form with cocoon, is awesome !! But I'm facing to an interrogation and I don't know which way I should take.
Relations :
Ranch have Cows
Ranch have Productions
Production have nested form Iproductions
What I'm trying to do now is implement my iteration |c| into each of my iproductions:cow_id elements
My code:
(My_form production)=
<%- #cows.each do |c| %>
<div class="row">
<div class="col-md-12">
<div id="Order">
<%= f.simple_fields_for :iproductions do |ip| %>
<%= render 'iproductions_fields', f: ip, cow: c %>
<%end%>
<div class="Order_links">
<%= link_to_add_association c.name, f, :iproductions, cow: c, class: "btn btn-default" %>
</div>
</div>
</div>
</div>
(My iproductions_fields)=
<div class="form-inline clearfix">
<div class="nested-fields">
<%= f.input :cow_id, :input_html => { :cow_id => :cow } %>
<%= f.input :quantity, input_html: {class: "form-input form-control"} %>
<%= link_to_remove_association "Supprimer", f, class: "form-button btn btn-default" %>
</div>
</div>
(My productions_controllers)=
class ProductionsController < ApplicationController
before_action :authenticate_user!
skip_before_action :configure_sign_up_params
before_action :set_ranch
before_action :set_production, except: [:create, :new, :show]
before_action :set_production2, only: [:show]
# GET /productions
# GET /productions.json
def index
#productions = Production.all
end
# GET /productions/1
# GET /productions/1.json
def show
#iproductions = Iproduction.where(id: params[:production_id])
end
# GET /productions/new
def new
#production = #ranch.productions.build
#cows = #ranch.cows
end
# GET /productions/1/edit
def edit
#cows = #ranch.cows
end
# POST /productions
# POST /productions.json
def create
#production = #ranch.productions.create(production_params)
#production.update(date: Date.today)
#cows = #ranch.cows
respond_to do |format|
if #production.save
format.html { redirect_to ranch_production_path(#production.ranch_id, #production), notice: 'Production was successfully created.' }
format.json { render :show, status: :created, location: #production }
else
format.html { render :new }
format.json { render json: #production.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /productions/1
# PATCH/PUT /productions/1.json
def update
respond_to do |format|
if #production.update(production_params)
format.html { redirect_to ranch_production_path(#production.ranch_id, #production), notice: 'Production was successfully updated.' }
format.json { render :show, status: :ok, location: #production }
else
format.html { render :edit }
format.json { render json: #production.errors, status: :unprocessable_entity }
end
end
end
# DELETE /productions/1
# DELETE /productions/1.json
def destroy
#production.destroy
respond_to do |format|
format.html { redirect_to ranch_productions_path(#production.ranch_id), notice: 'Production was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_ranch
#ranch = Ranch.find(params[:ranch_id])
end
def set_production2
#production = Production.find_by(id: params[:id])
end
def set_production
#production = #ranch.productions.find_by(id: params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def production_params
params.require(:production).permit(:name, :ranch_id, :created_at, :date, iproductions_attributes: [:id, :date, :cow_id, :quantity, :_destroy])
end
end
My real view , so I would like to Assign each iproduction with the button which generate the iproductions_fields
So if you have any idea of which way I can try to achieve this goal, you're welcome,
Thanks in advance

Désactivate the oldest post

I'm new in rails and I'm trying to build a web app, but I encounter the limits of my abilities.
I have a classic app with user who have posts, and to put this post in public, I create a model and a controller Online.
That I'm trying to do now, It's to change the boolean :push(from Online) in false if a new Online is created with the same post_id as before.
So if I push again my post 2, the previews Online associated with the post 2 will update their :push in false.
Well, if you have any ideas about that, let me know !
Thanks
My code =
Controllers:
Onlines :
class OnlinesController < ApplicationController
before_action :authenticate_user!
before_action :set_post
before_action :owned_online, only: [:new, :edit, :update]
before_action :set_online
def new
#online = current_user.onlines.build
#online.post_id = #post.id
#online.user_id = current_user.id
end
def create
#online.user_id = current_user.id
#online.post_id = #post.id
if Online.where(post_id: params[:post_id]).any?
#online = Online.where(post_id: params[:post_id]).last.update_attributes(push: false)
end
#online = #post.onlines.create(online_params)
if #online.save
if #online.portion <= 0
#online.update(push: false)
flash[:success] = 'Veuillez indiquer le nombre de parts disponibles '
redirect_to root_path
else
#online.update(pushed_at: Time.zone.now)
#online.update(push: true)
flash[:success] = 'Votre post est en ligne !'
redirect_to root_path
end
else
render 'new'
end
end
def show
end
def update
if #onlines.update(online_params)
if #online.push == false
if #online.portion <= 1
#online.update(push: false)
flash[:success] = 'Veuillez indiquer le nombre de parts disponibles '
redirect_to root_path
else
#online.update(push: true)
flash[:success] = 'Votre post a bien été pushé !'
redirect_to root_path
end
end
else
#user.errors.full_messages
flash[:error] = #user.errors.full_messages
render :edit
end
end
private
def online_params
params.require(:online).permit(:user_id, :post_id, :prix, :portion, :push, :pushed_at)
end
def owned_online
#post = Post.find(params[:post_id])
unless current_user == #post.user
flash[:alert] = "That post doesn't belong to you!"
redirect_to :back
end
end
def set_post
#post = Post.find_by(params[:post_id])
end
def set_online
#post = Post.find(params[:post_id])
#online = Online.find_by(params[:id])
end
end
Posts :
class PostsController < ApplicationController
before_action :authenticate_user!
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :set_online
before_action :owned_post, only: [:edit, :update, :destroy]
# GET /posts
# GET /posts.json
def index
#posts = Post.push_posts
end
# GET /posts/1
# GET /posts/1.json
def show
end
# GET /posts/new
def new
#post = current_user.posts.build
end
# GET /posts/1/edit
def edit
end
# POST /posts
# POST /posts.json
def create
#post = current_user.posts.build(post_params)
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: #post }
else
format.html { render :new }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /posts/1
# PATCH/PUT /posts/1.json
def update
respond_to do |format|
if #post.update(post_params)
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { render :show, status: :ok, location: #post }
else
format.html { render :edit }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
#post = Post.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(:user_id, :title, :description, :image, ingredients_attributes: [:id, :name, :_destroy])
end
def owned_post
unless current_user == #post.user
flash[:alert] = "That post doesn't belong to you!"
redirect_to root_path
end
end
def set_online
#onlines = Online.find_by(params[:id])
end
end
Models
Online :
class Online < ActiveRecord::Base
belongs_to :post
belongs_to :user
scope :push, ->{ where(push: true).order("pushed_at DESC") }
end
Post :
class Post < ActiveRecord::Base
belongs_to :user
has_many :onlines, dependent: :destroy
scope :push_posts, lambda { joins(:onlines).merge(Online.push) }
has_many :ingredients, dependent: :destroy
accepts_nested_attributes_for :ingredients, reject_if: :all_blank, allow_destroy: true
has_many :comments
validates :image, presence: true
has_attached_file :image, styles: { medium: "300x300#"}, default_url: "/images/:style/missing.png"
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
end
& finaly, the view to display the index of post :
<div class="row">
<%- #posts.each do |post|%>
<%- if post.onlines.last.push == true %>
<div class="post">
<div class="form-group text-center">
<h3> <%=post.title%></h3>
</div>
<p> Posted by : <%= link_to post.user.pseudo, profile_path(post.user.pseudo) %>, <%= time_ago_in_words(post.onlines.last.pushed_at) %> ago </p>
<p><%= post.onlines.last.prix %></p>
<p><%= post.onlines.last.portion %></p>
<div class="image text-center">
<div class="image-border">
<%= link_to (image_tag post.image.url(:medium), class: 'img-responsive'), post_path(post)%>
</div>
</div>
</div>
<% end %>
<%end%>
</div>
</div>
</div>
Thanks for your times !!
You probably would put it into the controller action where you can use
YourModel.where(post_id: params[:post_id]).update_all(push: false)
or something similar. It kind of depends what you want to do if you create more than 2 items with the same post_id

Categories