I've been following this railscast: https://www.youtube.com/watch?v=t_FNKR7jahM for learning how to implement nested attributes into my application. When I implement his code in the certain areas necessary I always get the same NoMethod error on the fields.
On the events show page where you can add songs, I am trying to add a 'Add Field' link that will add another group of songs(a field for artist, title, and genre).
Here is my EventController:
class EventsController < ApplicationController
def new
#event = Event.new
#event.songs.build
end
def index
#songs = Song.all
end
def show
#event = Event.find(params[:id])
#songs = #event.songs.paginate(page: params[:page])
end
def create
#event = current_user.events.build(event_params)
if #event.save
flash[:success] = "Event Created!"
redirect_to user_path(#event.user)
else
render 'welcome#index'
end
end
def destroy
end
private
def event_params
params.require(:event).permit(:name, :partycode, song_attributes: [:artist, :title, :genre])
end
end
Here is my Event model:
class Event < ApplicationRecord
belongs_to :user
has_many :songs, dependent: :destroy
accepts_nested_attributes_for :songs, allow_destroy: true
validates :name, presence: true
validates :partycode, presence: true, length: {minimum: 5}
end
Here is the show page in which the songs are added:
<section class="song_form">
<%= render 'shared/song_form' %>
<%= form_for #event do |f| %>
<%= f.fields_for :fields do |builder| %>
<%= render 'events/songs_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Add Field", f, :fields %>
<% end %>
</section>
Here is the song_fields file:
<fieldset>
<%= f.select :field_type, %w[text_field check_box] %>
<%= f.text_field :artist, placeholder: "Artist" %>
</fieldset>
Here is the ApplicationHelper file :
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
Finally, here is my events coffee script file;
$(document).on 'click', 'form .add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
Sorry for the lengthy post, an answer to my question would be GREATLY appreciated. Let me know if there is anything else you need from me(note I am using rails 5). Cheers :)
Should be
f.fields_for :songs do |builder|
Related
I'm building an events app using Rails 5.0 and have comments as a nested resource. Users can create and destroy comments, I'm trying to implement the edit/update function using Ajax/ remote: true so they can update a comment on the same page but it's not working. When I click on the edit link nothing happens. Here's the relevant code -
comments_controller.rb
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
def create
#event = Event.find(params[:event_id])
#comment = #event.comments.create(comment_params)
#comment.user_id = current_user.id
if #comment.save
redirect_to #event
else
render 'new'
end
end
# GET /comments/1/edit
def edit
#event = #comment.event
#comment = #event.comments.find(params[:id])
respond_to do |format|
format.html { render :edit }
format.js {}
end
end
def show
end
def update
if #comment.update(comment_params)
redirect_to #event, notice: "Comment was successfully updated!"
else
render 'edit'
end
respond_to do |f|
format.html { redirect_to #event, notice: "Comment Successfully updated!" }
format.js # render 'comments/update.js.erb'
end
end
def destroy
#event = Event.find(params[:event_id])
#comment = #event.comments.find(params[:id])
#comment.destroy
redirect_to event_path(#event)
end
private
def set_comment
#comment = Comment.find(params[:id])
end
def comment_params
params.require(:comment).permit(:name, :body)
end
end
_comment.html.erb
<div class="comment clearfix">
<div class="comment_content">
<div id="comments" class="comment">
<p id="comment_name"><strong><%= #comment.name %></strong></p>
<p id="comment_body"><%= #comment.body %></p>
</div>
<p><%= link_to 'Edit', edit_event_comment_path(comment.event), id: "comments", remote: true %></p>
<p><%= link_to 'Delete', comment.event,
method: :delete,
class: "button",
data: { confirm: 'Are you sure?' } %></p>
</div>
</div>
update.js.erb
$('#comments').append("<%= j render #comment %>");
edit.js.erb
$('#comments').html("<%= j render 'form' %>");
_form.html.erb
<%= simple_form_for([#event, #comment], remote: true) do |f| %>
<%= f.label :comment %><br>
<%= f.text_area :body %><br>
<br>
<%= f.button :submit, label: 'Add Comment', class: "btn btn-primary" %>
<% end %>
I've never implemented this action before using Ajax so I'm probably making a few schoolboy errors here. Any assistance appreciated.
You are calling edit method on controller with this
<%= link_to 'Edit', [comment.event, comment], id: "comment", remote: true %>
And you have no edit.js.erb
For updating your comment, you would have to create a form with it's action url pointing to your update method, and marking it as remote true. Then when you submit, it will reach update directly, there is no need to pass through edit method.
There is a method for creating forms with ajax option as default called form_with, you can check it's guide and documentation here:
http://guides.rubyonrails.org/working_with_javascript_in_rails.html#form-with
Updating answer after your question update
Your form would need to become something like this
<%= simple_form_for :comment, :url => "/events/#{comment.event_id}/comments/#{comment.id}", :method => :put do |f| %>
<%= f.label :comment %><br>
<%= f.text_area :body %><br>
<br>
<%= f.button :submit, label: 'Add Comment', class: "btn btn-primary" %>
<% end %>
I was working with nested attributes, everything seemed to be fine until when I submitted my information and I got this error. It says it is in my EventsController file:
class EventsController < ApplicationController
def new
#event = Event.new
#event.songs.build
end
def index
#songs = Song.all
end
def show
#event = Event.find(params[:id])
#songs = #event.songs.paginate(page: params[:page])
end
def create
#event = current_user.events.build(event_params)
if #event.save
flash[:success] = "Event Created!"
redirect_to user_path(#event.user)
else
render 'welcome#index'
end
end
def destroy
end
private
def event_params
params.require(:event).permit(:name, :partycode, song_attributes: [:event_id, :artist, :title, :genre, :partycode])
end
end
Here is my new.html.erb file in my songs view(handles song submission based on selected event)
<br>
<br>
<div class ="container">
<div class="jumbotron">
<%= form_for Event.new do |f| %>
<h3>Enter a song:</h3>
<%= f.fields_for :songs, Song.new do |song_form| %>
<%= song_form.collection_select(:event_id, Event.all, :id, :name) %>
<%= song_form.text_field :artist, placeholder: "Artist" %>
<%= song_form.text_field :title, placeholder: "Title" %>
<%= song_form.text_field :genre, placeholder: "Genre" %>
<% end %>
<%= link_to_add_fields "Add Song", f, :songs %>
<%= f.text_field :partycode %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
The link_to_add_fields method is defined in my ApplicationHelper.rb file:
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render("songs_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
current_user is defined in Session_helper.rb file:
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
def createEvent(event)
session[:event_id] = event.id
end
# Returns the current logged-in user (if any).
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
def log_out
session.delete(:user_id)
#current_user = nil
end
end
Finally, here is my songs_fields file that generates fields only when a user clicks a link that says 'Add songs'
<fieldset>
<%= f.text_field :artist, placeholder: "Artist" %>
<%= f.text_field :title, placeholder: "Title" %>
<%= f.text_field :genre, placeholder: "Genre" %>
</fieldset>
I feel as though this is the last portion before I get everything in my app to work! So help on this would be tremendous :D
You answered your own question... if you're not logged in, current_user will be nil so you will get this error.
Option 1 (ugly): change your logic so current_user.events doesn't get called if current_user is nil, and just go straight to the render
Option 2 (better): use a before_action statement to force current_user to be set before the action is run. Depends on what you're using to authenticate, but with Devise it would look like this:
class EventsController < ApplicationController
before_action :authenticate_user!
I think maybe:
class EventsController < ApplicationController
before_action :log_in(user)
might do it for you.
I've been struggling with this issue for weeks and haven't been able to find or figure out a solution. Here is what I'm trying to accomplish: a form that is automatically populated with a list of questions based on user input. Specifically, I have a list of all possible questions (PossibleQuestion model) that I want to use to populate the form based on the site a user selects.
I know I'm not rendering a proper response to the ajax request. I'm currently getting an undefined local variable or method f error. I found several SO questions regarding passing the form builder (here and here), however none of them have helped.
I'm still new to ruby and rails, so any help or direction is greatly appreciated!
Versions (ruby 2.3, rails 4.2.2). Also using SimpleForm and Cocoon gems.
report.rb
class Report < ActiveRecord::Base
belongs_to :site
has_many :questions, :dependent => :destroy
accepts_nested_attributes_for :questions, :allow_destroy => true
end
site.rb
class Site < ActiveRecord::Base
has_many :reports
validates :state, presence: true
end
question.rb
class Question < ActiveRecord::Base
belongs_to :report
has_many :answers, :dependent => :destroy
accepts_nested_attributes_for :answers, :allow_destroy => true
end
possible_question.rb
class PossibleQuestion < ActiveRecord::Base
validates :content, :state, presence: true
end
answer.rb
class Answer < ActiveRecord::Base
belongs_to :question
end
reports_controller.rb
def new
#report = Report.new
#questions = PossibleQuestion.all
#questions.each do |q|
question = #report.questions.build
question.content = q.content
question.answers.build
end
end
def questions_list
#site = Site.find(params[:id])
#state = #site.state
#questions = PossibleQuestion.where(state: #state).to_a
respond_to do |format|
format.js
end
end
report.js
$(document).ready(function() {
$('#report_site_id').change(function() {
site_id = $('#report_site_id').find(':selected').val();
$.ajax({
url: '/questions_list',
data: { id: site_id, },
questions_list.js.erb
$(".questions").html("<%= j render 'question_fields', :locals => { :questions => #questions } %>");
I've tried passing the formbuilder object f here in locals, however it doesn't work. I get an error that f is undefined.
_question_fields.html.erb
<%= f.simple_fields_for :questions do |builder| %>
<%= f.label f.object.content %>
<%= f.hidden_field :content, :value => f.object.content %>
<%= f.simple_fields_for :answers do |builder| %>
<%= render 'answer_fields', :f => builder %>
<% end %>
<% end %>
_form.html.erb
<%= simple_form_for #report do |f| %>
<%= f.association :site %>
<div class="questions"></div>
<% end %>
I have a model of a brand that can have many products that can have many categories. I have a nested form to create products that permit nested attributes for creating categories. But I can make it work.
class Brand < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :products, dependent: :destroy
validates :name, presence: true,
length: { maximum: 50 }
end
class Product < ActiveRecord::Base
belongs_to :brand
has_many :categories, dependent: :destroy
accepts_nested_attributes_for :categories
default_scope -> { order(created_at: :desc) }
validates :brand_id, presence: true
validates :name, presence: true,
length: { maximum: 50 }
private
def product_params
params.require(:product).permit(:name,
categories_attributes: [:name, :price])
end
end
class Category < ActiveRecord::Base
belongs_to :product
has_many :units, dependent: :destroy
validates :price, presence: true
validates :product_id, presence: true
validates :name, presence: true,
length: { maximum: 50 }
end
So my product controller is:
class ProductsController < ApplicationController
def new
#product = current_brand.products.new
#product.categories.build
end
def create
#product = current_brand.products.build(product_params)
if #product.save
redirect_to root_url
else
render 'new'
end
end
and my new view is like this:
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#product) do |f| %>
<%= render 'shared/error_messages_products' %>
<%= f.label :name, "Name:" %>
<%= f.text_field :name, class: 'form-control' %>
<%= link_to_add_fields "Add Category", f, :categories %>
<%= f.submit "Add Product", class: "btn btn-primary" %>
<% end %>
</div>
</div>
and my category partial is:
<fieldset>
<%= f.label :name, "Category Name" %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :price, "Price" %>
<%= f.text_field :price, class: 'form-control' %>
<hr>
</fieldset>
I have the link_to_add_fields helper in my application helper:
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
That allows me to use some Javascript to add category fields with:
jQuery ->
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
But when I try to add a product with any number of categories in this example 2, I fail to create the products and the categories. I get the error from my form and object error:
The form contains 1 error:
Categories product can't be blank
The params I get from this submition are:
{"utf8"=>"✓", "authenticity_token"=>"IO8GFcv1auFVh/ZNypONI78XQrY2Ntm07cMrrjmq51ogwppbsb1sNyN/ynKY+Pdb/lyniED9O6jFRkLKsvu2jQ==", "product"=>{"name"=>"Product Example", "categories_attributes"=>{"1467231299616"=>{"name"=>"Category Example 1", "price"=>"1234"}, "1467231300745"=>{"name"=>"Category Example 2", "price"=>"1234"}}}, "commit"=>"Agregar Producto", "controller"=>"products", "action"=>"create"}
I don't understand why the category and the product are not associating correctly.
After a while of experimenting I found that the answer is to remove the validation of product_id from the category model. Like this:
class Category < ActiveRecord::Base
belongs_to :product
has_many :units, dependent: :destroy
validates :price, presence: true
validates :name, presence: true,
length: { maximum: 50 }
end
I am in the early stages of creating an app, and am just putting some basic code in place. Here is the current code...
app/views/cards/front.html.erb
<%= form_for(front_of_card_path) do |f| %>
<%= f.fields_for :competency_templates do |builder| %>
<%= render 'add_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Add New Tag", f, :skill %>
<% end %>
routes
controller :cards do
get '/front', action: 'front', as: 'front_of_card'
post '/save', action: 'create', as: 'save_card'
get '/my_contact_info', action: 'back', as: 'back_of_card'
put '/save', action: 'update', as: 'save_card'
get '/my_card', action: 'show', as: 'card'
end
controller
def create
#skill= Skill.new(params[:skill])
#tag = Tag.new(params[:tag])
#tag.save
#skill.tag_id = #tag.id
#skill.save
redirect_to front_of_card_path, notice: 'Skill was successfully created.'
#get user/session
#save skills & tags
end
cards.js.coffee
jQuery ->
$('form').on 'click', '.remove_fields', (event) ->
$(this).prev('input[type=hidden]').val('1')
$(this).closest('fieldset').hide()
event.preventDefault()
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
app_helper
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
So right now this code gives me two text fields. One for the a tag name and another for a tag weight, and the controller inserts everything in the DB. I would like use some javascript to dynamically add as many of these tag/weight fields as I like. Everything I've found seems to focus on nested attributes. Any ideas appreciated.
Update
Added more code to flesh this out. The issue I am having is the 3rd variable I am passing in on this line...
<%= link_to_add_fields "Add New Tag", f, :skill %>
It does not like ':skill', but I am not sure what I should be passing here.
So here is what I came up with...here are my two models...
class Skill < ActiveRecord::Base
belongs_to :tag
attr_accessible :tag_id, :weight
end
class Tag < ActiveRecord::Base
has_many :skills
attr_accessible :name
end
I'm calling a partial from app/views/skills/_form.html.erb and using a js tag to add new fields. Also note that I am re-rendering the partial, then hiding it in the last div tag.
<div id="skillSet">
<%= render partial: "skills_form" %>
</div>
Add New Tag
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<div class="hide" id="new_skills_form">
<%= render partial: "skills_form", locals: {skill: false} %>
</div>
The partial is pretty simple. All I am doing here is storing the values in an array...
<div class="skillsForm">
<%= label_tag 'tag' %>
<%= text_field_tag 'tags[]' %>
<%= label_tag 'weight' %>
<%= text_field_tag 'weights[]' %>
</div>
...here is the javascript...real straight forward, just say when #addNewTag is clicked, appeand #new_skills_form to #skillSet
$(document).ready(function(){
$("#addNewTag").click(function(){
$("#skillSet").append($("#new_skills_form").html());
});
});
...and finally the controller action decontructs the arrays, and saves them...
def create
#skill = Skill.new(params[:skill])
tags = params[:tags]
weights = params[:weights]
tags.each_with_index do |tag, index|
tag = Tag.create :name => tag
Skill.create :tag_id => tag.id, :weight => weights[index]
end
end