I've been following a pretty standard UJS model for a lot of my AJAX and it works pretty well - however there's one situation where I'm not sure exactly what the best practice is. I have a model with a has_many relationship say:
class User < ActiveRecord::Base
has_many :aliases
accepts_nested_attributes_for :aliases, :allow_destroy => true
end
class Alias < ActiveRecord::Base
belongs_to :User
end
Now, in my NEW view for a User object, I'd like the user to be able to click a button and add as many aliases as they like - each time they press the button it fires off a remote request to a controller action that looks like this:
def new_ajax
#alias = Alias.new({})
respond_to do |format|
format.js
end
end
This replies with my new_ajax.js.erb template which looks like this:
$('#aliases-container').append('<%= j(render 'ajax_new')%>');
Which in turn renders a template which looks like this:
<%=simple_fields_for #alias do |f|%>
<%= f.input :name %>
<% end %>
This all works and I get my Alias form field rendered - but the problem is the ID and the NAME attribute are not set correctly in the nested fields. The ID and the Alias should be numbered in sequence to provide for proper association building in the create method and for error handling. Does anyone have a solution to this problem?
The problem you have is the base problem with adding new nested fields.
The simple answer is to use child_index: Time.now.to_i for your fields_for; however, you've got to invoke it within a form_for object so that Rails creates the names etc correctly. Sounds complicated but it's quite simple when you get your head around it:
#app/controllers/users__controller.rb
class UsersController < ApplicationController
respond_to :js, :html, only: :new
def new
#user = User.new
#user.aliases.build if request.xhr?
respond_with #user
end
end
#app/views/users/new.html.erb
<%= render "form", locals: {user: #user} %>
#app/views/users/_form.html.erb
<%= form_for user do |f| %>
<%= f.fields_for :aliases, child_index: Time.now.to_i do |a| %>
<%= a.text_field :name %>
<% end %>
<% end %>
#app/views/users/new.js.erb
var data = $("<%=j render "users/ajax_new", locals: {user: #user} %>").html();
$('#aliases-container').append(data);
You can see a more rudimentary implementation I wrote back in 2013:
Rails accepts_nested_attributes_for with f.fields_for and AJAX
Related
I have looked at various answers to similar questions and haven't quite cracked it.
A wine model is defined with has_one :register, :dependent => :destroy and rightly or wrongly I have added accepts_nested_attributes_for :register. A register is defined with belongs_to :wine.
The code within wines_controller.rb for create is:
def new
#wine = Wine.new
#register = Register.new
def create
#wine = Wine.new(wine_params)
#register = #wine.registers.build(register_params)
respond_to do |format|
if #wine.save
#success
else
format.json { render json: #wine.errors, status: :unprocessable_entity }
format.json { render json: #register.errors, status: :unprocessable_entity }
end
end
end
My form for creating a new wine has the following code:
<%= simple_form_for #wine do |f| %>
# various working elements
<div class="field">
<% f.fields_for :register do |r| %>
<%= r.label :short_name %>
<%= r.text_field :short_name %>
<%= r.label :barcode %>
<%= r.text_field :barcode %>
<% end %>
</div>
When this form is called up no fields are created from the f.fields_for command but this block is executed because I can add test buttons within it to prove it is accessed.
If I try to create a wine I get the following error message:
undefined method `registers' for #<Wine:0x007f1204375330> Did you mean? register register= register_id
I believe that using .build is there to ensure data integrity: I don't want to create a wine that does not have a corresponding register. I have tried thinking about it nested attributes but that seems to be considered a bad plan by many. This current approach feels correct but I think I am missing some understanding of syntax at the very least.
At a later date it will be necessary to have other models linked to register that will not be associated to wines. I was considering a similar approach but I am happy to be told to rethink!
If I understand you correctly you have 2 issues:
Firstly fields for register aren't being displayed - this is partly because #wine.register is nil.
You should change your new action to:
def new
#wine = Wine.new
#wine.register = Register.new
In addition because you are using simple_form_for you will need to use simple_fields_for instead of fields_for
Your second issue that results in the exception tells you everything... you are trying to access #wine.registers, and not #wine.register
Change in your create method to:
#register = #wine.register.build(register_params)
This will fix that issue ... however ... all you really need to do is build the #wine object from your params - your params should be configured to permit the right nested attributes - if it is set up correctly the register object will also be built when building the #wine object.
Your model is already set to accept_nested_attributes and thus will also validate and save the register object when calling #wine.save - no need to explicitly save the register object.
You should have something like:
def wine_params
params.require(:wine).permit(
:attribute1, :attribute2,
register_attributes: [:id, :short_name, :barcode])
end
Try this
Wine and Register models
class Wine < ApplicationRecord
has_one :register, inverse_of: :wine, :dependent => :destroy
accepts_nested_attributes_for :register
end
class Register < ApplicationRecord
belongs_to :wine, inverse_of: :register
validates_presence_of :wine
end
Wines Controller
class WinesController < ApplicationController
def new
#wine = Wine.new
#wine.build_register
end
def create
#wine = Wine.new(wine_params)
if #wine.save
redirect_to #wine
else
render :new
end
end
private
def wine_params
params.require(:wine).permit(:name, register_attributes: [:simple_name])
end
end
My wine_params are specific for
rails g model wine name:string
rails g model register name:string wine_id:integer
Lastly wine form should look like this
<%= form_for #wine do |f|%>
<p>
<%= f.label :name%>
<%= f.text_field :name%>
</p>
<%= f.fields_for :register do |r|%>
<p>
<%= r.label :simple_name%>
<%= r.text_field :simple_name%>
</p>
<% end %>
<%= f.submit %>
<% end %>
So you can modify wine_params and form partial for your application specifics
guys i am suffering from a problem from last two days and i dont know whats going wrong with my code. I have two model with the name of 'post' and 'descrip'. Descrip belongs_to post as post has_one descrip. So when i submit form for post then next form appear which is for descrip.I descrip form i have one hidden field to pass post_id. But when i submit descrip form an error appear that "POST can't be find without an id". Here below my code as
in view
<%= form_for :descrip, url:{action: "create", :controller => "descriptions"} do |f| %>
<li>
<%= f.label 'Detail' %><br>
<%= f.text_field :detail %>
</li>
<li>
<%= f.hidden_field :post_id , :value => #post.id %>
</li>
<li>
<%= f.submit %>
</li>
<% end %>
In Descriptions_controller
def new
#descrip = Descrip.new
end
def create
#post = Post.find(params[:descrip][:post_id])
#descrip = #post.descrips.build(descrip_params)
if #descrip.save
render #post
else
render 'new'
end
end
model of post and descrip
class Post < ActiveRecord::Base
has_one :descrip, :dependent => :destroy
end
class Descrip < ActiveRecord::Base
belongs_to :post
end
resources.rb
resources :descriptions
get "descriptions/new", :to => "descriptions#new", as: :descriptions_new
post "descriptions/create/:id", :to => "descriptions#create", as::descriptions_create
resources :posts do
resources :descriptions
end
Kindly suggest me what i should do.How i can solve my error and how i can get post_id in my decrips table.Please.
It seems like there are two possibilities.
1) Firstly, it could be that your params are not what you think they are and plugging in an invalid number for Post.find(). Could you share what your params looks like?
2) If that's not the case you could be calling id on a post that isn't yet saved and therefore can't be found by ActiveRecord within your database. If you're submitting this form along with a form to create a post, you're likely saving the description first. Make sure your post either exists in the database or is saved before the description to fix this.
Out of curiosity is there any reason you've made description a separate table from post? It seems like you could save yourself a lot of trouble just having it be a text column within the posts table.
in your action add logger.info:
def create
logger.info "---params ==> #{params.inspect}---"
#post = Post.find(params[:descrip][:post_id])
#descrip = #post.descrips.build(descrip_params)
if #descrip.save
render #post
else
render 'new'
end
end
run the application then in log/development.rb file you can view the all params value.
if params or not passing try with
<%= hidden_field_tag 'descrip[post_id]', #post.id %>
I've followed this Railscast on submitting a form via Ajax and updating a div without reloading the page, but I'm having trouble with one portion of it.
Ryan has $("#products").html("<%= escape_javascript(render(#products)) %>"); in an index.js.erb file to update the #products div when the form is submitted. I'm struggling to understand whether an instance variable like #products is relevant for my situation, or if I can simply substitute it for a URL.
I'm trying to do the same thing as Ryan in this screencast, but instead of display search results I just want to display the updated value.
In show.html.erb I have:
<% #project.project_todos.order("created_at DESC").where(:status => false).each do |todo|%>
<%= form_for todo, :remote => true, :"data-replace" => "#dueon" do |f| %>
<%= f.text_field :due %>
<%= f.submit :class => "primary", :value => "Add" %>
<% end %>
<div id="dueon">
<%= render "duedate", :todo => todo %>
</div>
<% end %>
The partial _duedate.html.erb has one line in it: <%= todo.due %>
So in my index.js.erb I currently have this: $("#dueon").html("<%= escape_javascript(render("_duedate")) %>"); but it's clearly not working. Do I have to use a variable here in place of the _duedate? And if so, how would I set this up in the controller? I mean what does the variable have represent?
Also, for what it's worth, the partial is rendering correctly and displaying the todo.due value...it's just not updating when I submit the form.
ProjectsController:
def show
#project = Project.find(params[:id])
# Display the form to create a new todo
#project_todo = ProjectTodo.new
respond_to do |format|
format.html # show.html.erb
format.json { render :json => #project }
end
end
Try this
in you controller action , (say sample_action)
def sample_action
#todos = #your code
respond_to do |format|
format.js
end
end
and you have a sample_action.js.erb
$("#dueon").html("<%= raw escape_javascript(render(:partial => 'duedate')) %>")
then inside the partial, you have access to the new #todos instance variable
HTH
I will answer you separately as I believe your entire setup should be little change (IMO, this might not be the case)
I think you should have a todos controller with a project belongs to it,
--- models ----------------
class Project < ActiveRecord::Base
has_many :todos
end
class Todo < ActiveRecord::Base
belongs_to :project
end
---- routes ---------------
resources :projects do
resources :todos do
end
end
---- controllers ----------
class ProjectsController < ApplicationController
end
class TodosController < ApplicationController
def new
#project = Project.find(params[:project_id])
#todos = #project.todos.build
end
end
in your view (views/todos.html.erb)
<%= #project.name %>
<%= form_for([#Project, #todos]) do |f| %>
#todo form code
<% end%>
As per the relation, project has many todos, its always clear to show the project details in the todo add screen, rather than allowing users to add new todos from project screen.
and again, this is my personal view, feel free to ask any questions :)
I'm getting the following error in my rails app:
comparison of User with User failed
The relevant section of my controller looks like this:
class AssessmentsController < ApplicationController
before_filter :authenticate_user!
respond_to :html, :xml, :js, :pdf
def index
#user = current_user
#account = Account.find(#user.account_id)
#assessments = Assessment.all
respond_with #assessments
end
The relevant section of my view looks like this:
<%= form_for(#account) do |a| %>
<%= a.fields_for :users, #account.users.build do |u| %>
....
<%= a.submit "Sign-up", :class => "button", :disable_with => "Saving..." %>
<% end %>
<h1>Current users</h1>
<% for #user in #account.users.sort! { |b,a| a.id <=> b.id } %>
<%= render :partial => 'user' %>
<% end %>
The error seems to be originating around the for #user in #account.users.sort! section according to the error model, but removing it seems to be the addition of the #account.users.build in the fields_for section that creates it (but I need this as I want the user to be able to create a new user for that account. Can someone enlighten me to what is generating this?
The error is indeed occurring on that line, because ActiveRecord models don't implement comparables by default. So when you say #account.users.sort!, the sort bombs out since it has no way to compare users with users.
There's two things you can do here:
Implement the comparison operator for your user model. Check out this link for a blog post on how to do it, but it'd be something like:
class User < ActiveRecord::Base
def <=>(other)
self.name <=> other.name
end
end
Tell the sort directly what comparison to use, like this:
#account.users.sort! {|a, b| a.name <=> b.name}
The models project and category are in a has_and_belongs_to_many relationship. The partial seen below is used on different views to show a dropdown-menu with all the available categories. The projects in the list below the dropdown-menu are shown according to the choice the user made in the dropdown-menu.
Besides other categories, there is a category named "Everything" that every project is member of. It's also the first entry in the category-db-table, because it got inserted in while loading the migrations into the database.
Right now there is no error, but regardless of which category I choose it reloads the page showing the "Everything" category.
Any idea what I need to change in the code mentioned below to make it work the way I want to? Thanks for your help!
Partial with dropdown-menu and project-list
<!-- category dropdown -->
<% form_for category_url(:id), :html => {:method => :get} do |f| %>
<label>Categories</label>
<%= f.collection_select(:category_ids , Category.find(:all), :id , :name) %>
<%= f.submit "Show" %>
<% end %>
<!-- project list -->
<ul class="projectlist">
<% #projects.each do |_project| %>
<li>
<%= link_to(_project.name, _project) %>
</li>
<% end %>
</ul>
Logoutput after choosing category with id 2 on the dropdown-menu
Processing ProjectsController#index (for 127.0.0.1 at 2009-02-20 17:26:10) [GET]
Parameters: {"commit"=>"Show", "http://localhost:3000/categories/id"=>{"category_ids"=>"2"}}
Category Model
class Category < ActiveRecord::Base
has_and_belongs_to_many :projects, :join_table => "categories_projects"
end
Categories Controller
class CategoriesController < ApplicationController
def show
#projects = Category.find(params[:id]).projects.find(:all)
respond_to do |format|
format.html # show.html.erb
end
end
end
Project Model
class Project < ActiveRecord::Base
has_and_belongs_to_many :categories, :join_table => "categories_projects"
end
Projects Controller
class ProjectsController < ApplicationController
def show
#projects = Project.find(:all)
#project = Project.find(params[:id])
respond_to do |format|
format.html # show.html.erb
end
end
def index
#projects = Project.find(:all)
respond_to do |format|
format.html # index.html.erb
end
end
end
part of 'rake routes' output
category GET /categories/:id {:controller=>"categories", :action=>"show"}
You're passing a parameter called :category_ids, but you're not accessing that anywhere.
form_for category_url(:id)
This will submit your form to the path /categories/1 or whatever id you're currently viewing. You're then using that :id for finding your category projects:
#projects = Category.find(params[:id]).projects.find(:all)
So you're just showing the same ones over again. Because it's a form, you're submitting a query with the :category_ids parameter:
POST /categories/1?category_ids=2
You could just change your Category.find to use the other parameter instead. But, normally to view category 2 you would just use the url /categories/2, where 2 is your :id parameter. You have two ids in that path, and you should decide how you want to resolve that.
One option is to use the categories_path for the form action, and change the collection_select :category_ids parameter to just :id:
/categories?id=2 # from the form
/categories/2 # from a link
But if you're just listing projects, I would move this logic into the projects controller (index action), so your URLs would look like:
/projects?category_id=2` # from the form
/categories/2/projects # from a link
Thanks Andrew, but I solved it myself this way:
I got rid of collection_select, changed to select, added the filter-action (with the according route in config/routes.rb) and now everything works as I expected.
...I'm trying to get an observer on the select-menu, that submits it's value to the filter-action as soon as the user changes the selection in the select-menu, but that's another story. ;-)
New partial with dropdown-menu and project-list
<% form_tag(filter_category_path(:id), :method => :post, :class => 'categories') do %>
<label>Categories</label>
<%= select_tag(:category, options_for_select(Category.all.map {|category| [category.name, category.id]}, #category.id)) %>
<%= submit_tag "Go" %>
<% end %>
<ul class="projects">
<% #projects.each do |_project| %>
<li>
<%= link_to(_project.name, _project) %>
</li>
<% end %>
</ul>
New categories controller
class CategoriesController < ApplicationController
def show
#category = Category.find(params[:id])
#projects = #category.projects.find(:all)
respond_to do |format|
format.html
end
end
def filter
#category = Category.find(params[:category])
#projects = #category.projects.find(:all)
respond_to do |format|
format.html
end
end
end