campaign.rb
class Campaign < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :item
end
item.rb
class Item < ActiveRecord::Base
belongs_to :campaign
end
Campaign has 2 attributes: title and description
Item has 1 attirubte: name
I'll try explain myself by words, I want to create a nested form where they user insert the campaign's name and description but he can insert more than just 1 item, he can insert a list of items (in particular there will be a "+" button that when clicked a new item row will appear and the user can insert items).
At the end all is send all together clicking just one submit button.
How can I reach my goal with rails?
I answered a question just yesterday. Here's the link: Rails accepts_nested_attributes_for with f.fields_for and AJAX
I'll write out how it works & provide some resources to help give you some more ideas:
How It Works
Loading associative fields into a form is done by using f.fields_for
You'll do it like this:
#app/views/campaigns/new.html.erb
<%= form_for #campaign do |f| %>
<%= f.fields_for :items do |a| %>
<%= a.text_field :information %>
<% end %>
<% end %>
In order to get this to work, you have to build the associated ActiveRecord objects in the backend before you render the view, like this:
#app/controllers/campaigns_controller.rb
def new
#campaign = Campaign.new
#campaign.items.build
end
Adding Extra Fields Via Ajax
Adding extra fields with Ajax requires engineering a new solution to the issue
The way you do this is to take the f.fields_for text & put it into a partial. This partial can be called from the original view, as well as another view (which we can render through Ajax)
The Ajax part works by basically taking a request from your form (the ajax request), and then using another action in your controller to build a new ActiveRecord object & render another partial that will contain another form. This partial will then call the original f.fields_for partial, allowing you to render another field
Your Ajax can then extract the new field & append it to your page. The way you get around the id issue (keeping the IDs sequential & unique) is to employ the child_index method, and use Time.now.to_i to generate a timestamp
If you read my answer referenced at the top of this answer, all of this will make sense :)
Some great resources for this:
RailsCasts Nested Forms
Adding Fields With Ajax
A nice gem along with tutorial is available from ryanbates who is the author of railscasts.com site.You can use this and have a look at tutorial here
And also if you want to try manually use the fields_for while writing in the form like here and manage some jquery code for add or remove.
Related
<%= form_for [#blog,#blog.comments.build] do |f| %>
<p><%= f.text_area :text, :size => '40x10' %> </p>
<p><%= f.submit "Post Comment" %> </p>
<% end %>
This is handler by comments_controller, but I would like to know the reason, especially for form_for
The form_for creates a form for creation or update of passed object. If the object is not persisted, the associated url will target the creation action. Otherwise the targeted action will the update. form_for can receive many different kinds of parameter to generate the form.
If you check out the Rails url_helpers documentation, you will see that you can do something like:
<%= link_to 'First comment', blog_comment_path([#blog, #blog.comments.first]) %>
This will generate a link to the first comment of the blog with a path like /posts/#post.id/comments/#post.comments.first.id. This also assumes that you have the correct setup on your routes.rb:
resources :blogs do
resources :comments
end
With this, you generate a bunch of paths that you can use to build, for instance, links and forms. Thus, the form_for in your code works similarly. Think of it as a url_helper. You have a #blog and a comment associated to the post(#blog.comments.build). As the comment is not persisted yet, the will generate a form for the creation of comments targetting CommentsController#create. The associated path will be something like /blogs/#blog.id/comments and the HTTP method will be POST.
Also, check these links to get more info:
Rails Routing
Rails Form Helpers
It adds a form with a text box, submit button and some hidden authentication related hidden fields for entering comment.
The comment is added to #blog object with relationship:
has_many :comments
Comment is build by the code if not present by:
#blog.comments.build
So overall you get a form for entering comments in a # blog object. The blog object is necessary in this case and the comment will be automatically combined to the blog entry in proper column in comment record column "blog_id" by default.
This is called Nested Form Relationship, where instead of editing only one record of comment you can combine the parent object also and edit it.
build is basically used to create a structure for object, something like new ( e.g. Model.new). Form action is decided on the basis of given objects. In your case the objects are #blog and #blog.comments.build so the action called will be of either update of Blog controller or Create of Comments Controller..
Hope this helps.
I'm using
class Mother < ActiveRecord::Base
has_many :boys
accepts_nested_attributes_for :boys, reject_if: :all_blank, allow_destroy: true
end
and
class Boy < ActiveRecord::Base
belongs_to :mother
attr_accessor :mother_id
end
because I can't modify columns of Boys table.
Now I create nested form in Rails but when I go to save Mother and Boys it says:
ActiveModel::MissingAttributeError - can't write unknown attribute `mother_id`:
And it is normal because the column mother_id doesn't exists. But I don't need to save Mother model, just the many boys in the form I use with gem cocoon and then delete immediately the mother's one that I don't need anymore.
How to bypass the inexistence of column mother_id in boy's table and just save the boys rows without references to "fake" mother?
Is there a way in Rails to create a form in which I can use a mechanism like Cocoon but with the same instance of the model and not a child one, without "accepts_nested_attributes_for" to be clear?
Have a Mother model is completely unnecessary in your case, also use cocoon is unnecessary because you do not need to save your parent model. What I would do is have a form just for create Boy's and if you want it to look "nested" form, then you can Ajaxify it for give it the appearance of nested.
Delete your Mother model, you do not need it, then add a basic set of routes for your Boy
routes.rb
resources :boys
boys_controller.rb
def create
respond_to do |format|
#boy = Boy.new boy_params
#boy.save
format.js
end
end
_form.rb
#just make sure you have remote: true and check that you are always creating a new Boy
<%= form_for Boy.new, builder: yourBuilder, remote: true do |f| %>
.....
<% end %>
Some where in your view add a div where you can show your boys and also for later append them each time you create a new one:
<div id="boys">
#Whatever collection you can get for show the current created boys I do not know, let's say current_user
<% current_user.boys.each do |boy| %>
<%= render boy %>
<% end %>
</div>
Next just add a script for your create action:
boys/create.js.erb
#If #boy is still not persisted it means it has errors, display errors
<% if #boy.new_record? %>
$('#boy_form').append("<span class=\"error\"><%= j #boy.errors.full_messages.join(', ').html_safe %></span>");
setTimeout(function() {
$('#new_group_goal .error').remove();
}, 8000);
<% else %>
#Else we prepend the #boy in the div #boys and that's it, you form is beautifully ajaxified.
$('#boys').prepend("<%= j render(#boy) %>");
setTimeout(function() {
$('# .error').remove();
}, 2000);
<% end %>
This way you have a unic form for create Boy's with the Ajax you can add boys dynamically and it will look like a nested form, just instead of have 1 submit action you perform a submit for create all the boys, you will perform 1 submit action for each boy and display it dynamically below the form, this way is more flexible in addition to that this way has better performance. Let me know if you understood my point.
I have a show page for an author that lists the authors books. I'd like to be able to add a book name in line to quickly add a new book association. I could do it pretty easily with rails MVC on the front end, but I am having a hard time undertsanding the activeadmin DSL to do things that are not quite out of the box.
Here's the current table of data on the show page, straight-forward.
show :title => :name do
panel "Books" do
table_for(author.books) do
column("Book") { |book| link_to book.name.titleize, admin_book_path(book) }
column("Release") { |book| book.release_date.to_formatted_s(:long_ordinal)}
column("Sales") { |book| book.orders.count }
end
end
end
What I would like to see is at the bottom of the list a blank textfield that I can input a new book title and click ADD to add it to the list. But I only see info in the docs on how to edit the form itself, which gets shown only on the edit page.
Overall I love how quickly activeadmin sets up, but it feels like I'm missing some basic concepts of how it functions, and the docs aren't helping me grasp it...
You could for example render a from as a partial in the Show page and add any needed controller actions to add books and redirect back to Show page.
row("Add book") do |item|
div do
render "form_to_add_books"
end
Just use associations in your models and use it like this
form do |f|
f.inputs do
f.input :book_name
f.inputs do
f.has_many :titles do |p|
p.input :title
end
end
end
f.has_many will automatically generates the button.
And don't forget to use
accepts_nested_attributes_for :titles
in your books model.
Experienced Java developer, new to Rails - wondering about belongs_to relationship in scaffolding.
Saw another answer like this
Does rails scaffold command support generate belongs_to or many to many model middle table migration info?
and followed the rails generate scaffold_controller obj:references pattern.
The index/show page is showing #<MyClass:xxxx> instead of the string I want - is there a method in the target class (parent side of the belongs_to) I need to override to specify the identifier?
Also in the edit view, it looks like it's trying to modify the reference as a string rather than as drop-down - is there something I need to specify to make that happen?
Thanks!
BTW - I was able to get similar scaffolding to work in Django and Grails, where the foreign key turned into a drop-down; I'm hoping Rails is equally easy and I'm just missing it.
You can override the #to_s method on the instances, as it is the one being called.
class FooDoodle < ActiveRecord::Base
def to_s
name
end
end
That's when showing a record.
However, when you're actually using the form to set the associations, scaffold will only generate an input field in the view so you can enter the id. You could have a dropdown menu for example, but the options for that dropdown would somehow have to be selected in a manner.
For example, if there are 2000 possible associated records, which ones do you show? Do you show the 2000? Only the first 10? That logic would go into your controller.
So, for example:
class FooDoodlesController < ApplicationController
def edit
#foodoodle = FooDoodle.find(params[:id])
#friends = #foodoodle.possible_friends # or else
end
end
and using select and options_for_select as choices
# _form.html.erb
<%= form_for #foodoodle do |f| %>
<%= f.label :friend %>
<%= f.select :friend, #friends.map{ |p| [p.to_s, p.id] } %>
I am making my own custom view that I need to make the process of creating associated models less painful for my users. I want to display all of the models associated pieces in-line, with controls to edit them. This is quite easy to roll my own for the basic fields, but I'd rather use a form_filtering_select partial for the inline model's associations, but I can't find any documentation to do this.
You can use Nested Form
Consider a User class which returns an array of Project instances from the projects reader method and responds to the projects_attributes= writer method:
class User
def projects
[#project1, #project2]
end
def projects_attributes=(attributes)
# Process the attributes hash
end
end
Note that the projects_attributes= writer method is in fact required for fields_for to correctly identify :projects as a collection, and the correct indices to be set in the form markup.
When projects is already an association on User you can use accepts_nested_attributes_for to define the writer method for you:
class User < ActiveRecord::Base
has_many :projects
accepts_nested_attributes_for :projects
end
This model can now be used with a nested fields_for. The block given to the nested fields_for call will be repeated for each instance in the collection:
<%= nested_form_for #user do |user_form| %>
...
<%= user_form.fields_for :projects do |project_fields| %>
<% if project_fields.object.active? %>
Name: <%= project_fields.text_field :name %>
<% end %>
<% end %>
...
<% end %>
Here goes the Reference for details.
There's a cool gem out there that does pretty much what you want. It's called Nested Form Fields. It allows you to edit records (along with their has_many associations) on a single page. The cool thing about it is that it even uses jQuery to dynamically add/remove form fields without a page reload. Checkout out the gems docs for proper usage. Hope that helps!