Using awesome_nested_set dynamically adding extra children - ajax

I've got a nested model called categories and I have created a nested form to allow creation of a category and sub categories in one form.
This works fine if you pre-build the children in your new method like so:
class CategoriesController < InheritedResources::Base
def new
#category = Category.new
#category.children.build
end
end
The problem starts to happen when you want to dynamically add new children in the form using AJAX.
Here is my form:
%table
= form_for #category do |f|
%tr
%td= f.label :name
%td= f.text_field :name
%tr
%td(colspan=2)
%b Sub categories
- #category.children.each do |sub|
= f.fields_for :children, sub do |child|
= render "child_fields", :f => child
%tr
%td= link_to_add_fields "Add sub category", f, :children
%tr
%td= f.submit 'Save'
Here is my helper method for link_to_add_fields (as per Ryans Railscast):
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
render(:partial => association.to_s.singularize + "_fields", :locals => { :f => builder})
end
link_to_function(name, "add_fields(this, '#{association}', '#{escape_javascript(fields)}')")
end
end
And here is the Javascript which over
function add_fields(link, association, content) {
// Generate new unique index, so base this off the current time.
var new_id = new Date().getTime();
var regexp = new RegExp("new_" + association, "g")
// Replace new_association with the current time.
$(link).closest("tr").before(content.replace(regexp, new_id));
}
I noticed that on the pre-built children the rendered output is like this:
<input type="text" size="30" name="category[children_attributes][0][name]" id="category_children_attributes_0_name">
Where as the AJAX generated fields are:
<input type="text" size="30" name="category[children_attributes][1308801890744][name]" id="category_children_attributes_1308801890744_name">
This looks correct but when I go to click create only the pre-built children are saved.
Update1
If I put a debugger line in my def create and type params I only see my pre-built category not the extra one I dynamically added.
(rdb:4) params
{"utf8"=>"✓", "authenticity_token"=>"iwq1Vx3jOZZsjd79Nj+qKNXxOwWP40c8XDFS8ooGMdg=", "category"=>{"name"=>"1", "children_attributes"=>{"0"=>{"name"=>"2"}}}, "commit"=>"Save", "action"=>"create", "controller"=>"categories"}

This is a result of browsers (at least Firefox) behaving weirdly when a form is inside of a table. The easiest/quickest fix is to put the table inside the form. It's a one-line change to your views/categories/new.haml file:
= form_for #category do |f|
%table
%tr
How I debugged it, in case it helps:
I first checked request.raw_post; the parameters weren't there which meant rails was never even seeing the correct request. That pointed to a browser rendering issue.
I was able to debug the issue via firebug by noticing that the form closed out awkwardly when rendering your original haml. Moving the form out of the table seemed to fix it in firefox.
I'd suggest sticking to divs, mainly because it avoids a lot of weird browser issues.

Related

How to create nested model from partial Rails 6

Newbie Here!
I'm unsuccessfully trying to submit a form of a nested model.
Aim:
Verify a translation by submitting a boolean to a reviews model to associate whether a translation is verified or not, with translation entry and user model associated references.
# routes.rb
resources :entries do
resources :reviews
end
# entry.rb
belongs_to :user
has_one :review
accepts_nested_attributes_for :review
# user.rb
has_many :entries
has_many :reviews
# review.rb
belongs_to :user
belongs_to :entry
From entry index, pass the entry instance to partial, works perfect
# /entries/index.html.erb
<% #entries.each do |entry| %>
...
<%= render 'reviews/new', entry: entry %>
...
<% end %>
Unsuccessfully setting up new/create form.
What happens is that the entry instance is well received, but I am failing to create a new model instance for review belonging to entry.
entry.review raises an nil error for review, while entry is fine when testing through browser console
First argument in form cannot contain nil or be empty
# reviews/_new.html.erb
<span>
<%= form_for entry.review do |f| %>
<div class="form-check form-switch">
<%= f.check_box :verified, class: "form-check-input" %>
</div>
<%= f.submit class: "btn btn-primary"%>
<% end %>
</span>
Another attempt was also to use just #review from the controller but that doesn't obey nested routes.
My controller looks like this
# reviews_controller.rb
def create
#entry = Entry.find(params[:entry_id])
#review = #entry.review.build(review_params)
#review.user_id = current_user.id
#review.save
end
private
def review_params
params.require(:review).permit(:verified, user: current_user, entry: #entry)
end
Am I suppose to implement my actions in the entries_controller?
I have also found the tutorial here useful but replication was unsuccessful.
Another StackOverflow reference here
I still get the error entry.review.build that review is nil.
First argument in form cannot contain nil or be empty
When building an associated record over a has_one relation, instead of
#review = entry.review.build(review_params)
you need to use the following:
#review = entry.build_review(review_params)
See the documentation for more details.
Am I suppose to implement my actions in the entries_controller?
It depends on what you're after. If you have a dedicated form for adding a new review and it is not embedded in another form for creating or updating an entry then implementing a create action in ReviewsController is the straightforward solution – in this case you should also not need accepts_nested_attributes_for in Entry.
If, however, you want to be able to create or update an entry as well as its review using the same form, then you should nest the review form in the form of the entry, keep accepts_nested_attributes_for, and use actions in EntriesController. The documentation should get you started there.

How to create multiple 'new' pages within a controller that behave the same with different text?

Ok - I have a simple application built that allows you to store action items based on business categories: Business Admin, Business Development, Business Financial, Etc.
Each one of the categories, will have the same exact form:
<div align="center"><h1>What are your important Business Admin Action Items?</h1>
<%= form_for #category do |f| %>
<p>
<p>Store Answer Below:</p>
<%= f.text_field :name, :size => 40, :style => 'height: 40px' %>
</p>
<p>
<%=f.submit 'Save action item' %>
</p>
<% end %> </div>
However, not all of these categories will have the same H1 tag (or other text/attributes within the view). So, how does that work?
Do I create new pages, that are: business_admin.html.erb, business_development.html.erb etc - and if so, I can't see how I'd use the same new action for all of them, especially since the answers to the user input are going to be stored on separate pages (the categories' respective pages: business_admin_storage.html.erb, etc.
I understand that naming the urls in that fashion may not be the best way, nor a conventional way. That's part of my question as well. How should I approach this situation. Will be trying a whole host of stuff in the meantime :) If I need to be more clear, please tell me.
New to rails. Loving every second.
Categories controller
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def new
#category = Category.new
end
def create
#category = Category.new(category_params)
#category.save
end
def show
end
def destroy
end
private
def category_params
params.require(:category).permit(:answer)
end
end
Index View
<h1>Select A Business Category To Begin Identifying Action Items</h1>
<ol><li><%= link_to 'Business Admin', new_category_path %></li><br><br>
<li><%= link_to 'Business Development/Marketing', new_category_path %></li><br><br>
<li><%= link_to 'Financial', new_category_path %></li>
</ol>
<%= link_to 'Store random action items', new_facilitate_path %>
Routes.rb
Rails.application.routes.draw do
resources :facilitates
resources :categories
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
# You can have the root of your site routed with "root"
root 'categories#index'
get 'show' => 'facilitates#show'
get 'index' => 'categories#index'
get 'business_admin' => 'categories#new'
SHOW
Stored !!
<h1>Listing stored action items for this category... </h1>
I can't see your model for Categories but it seems like you're varying the page served based on the name of the category?
Wouldn't something like this cover you?
<div align="center">
<h1>What are your important <%= #category.name %> Action Items?</h1>
</div>
You would need to pass the relevant #category to the relevant view via the controller, here is the Rails 4 way:
before_action :set_category, only: [:show, :edit, :update, :destroy]
...
def show
end
...
private
def set_category
#category = Category.find(params[:id])
end
And your Categories model would need to have a name property defined, which if you don't have it already could be accomplished by a migration.

Not showing created blog entries

I'm fairly new to Rails and learning to create a blog using this tutorial. On step 10, once I define create and show, after creating a new post in browser I don't see any entries on show with id page. All I see is heading and and blank title and post header.
Following is my controller -
class PostController < ApplicationController
def index
end
def new
end
def create
#post = Post.new(params[:posts])
#post.save
redirect_to #post
end
def show
#post = Post.find(params[:id])
end
end
Show view ---
<h1>Show a post</h1>
<p>
<strong>Title:</strong>
<%= #post.title %>
</p>
<p>
<strong>Text:</strong>
<%= #post.text %>
</p>
Route ---
RailsBlog::Application.routes.draw do
resources :post
root :to => "post#index"
end
Form ---
<%= form_for :post, url: {action: 'create'} do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit 'Submit' %>
</p>
<% end %>
May be this is just a spelling mistake, but since I've recently started learning Rails, I'm unable to resolve this.
Update: I can go to particular id using
http://localhost:3000/post/1
but am only seeing blank page with view headers
The problem is here:
#post = Post.new(params[:posts])
It should be params[:post] - singular, not plural.
Also note that the best practice with form_for is to pass an object instead of symbol:
form_for #post do |f|
Then:
You don't need to specify url
You can reuse the same form for an edit action or create action (if object creation failed due to failing validation)
This however requires to initialize new object in your new action:
def new
#post = Post.new
end
UPDATE:
Also your routes are incorrect. When defining plural resources, you need to use plural form (it's more the convention than requirement, but since you're learning stick with it). So change your routes to:
resources :posts
And rename your controller to PostsController (remember to rename file name as well). restart the server and all should work.
ANOTHER UPDATE:
You also need to rename folder app/views/post to app/view/posts.
AND YET ANOTHER UPDATE:
In rails 4, you are not allowed to mass assign any params which has not been whitelisted using strong parameters. You need to tell rails which fields you allow to be assigned first - this is a security thing. You need to make some changes to your controller:
class PostsController < ApplicationController
...
def create
#post = Post.new(post_params)
...
end
...
private
def post_params
params.require(:post).permit(:title, :text)
end
end
This is the way to tell your controller that you are expecting those attributes from your form and they can be safely assigned.
I had just similar problem on the same tutorial.
The code spelling was correct and clearly accorded to examples in tutorial and BroiSatse's answer above.
The mistake was in order of private method definition.
How it was:
...
private
def post_params
params.require(:post).permit(:title, :text)
end
def show
#post = Post.find(params[:id])
end
...
The working order:
def show
#post = Post.find(params[:id])
end
private
...
Anyway, this topic was rather helpful. Thak you for your answers!

No route matches [POST] "/story/new" after submitting form

I've just started "Build Your Own Ruby on Rails" and I have had to use Google a lot, as the book seems to have a bunch of places where the code just doesn't work. This time, I couldn't find an answer. Okay, so here's the deal. I have a form that looks like this:
new.html.erb:
<%= form_for :story do |f| %>
<p>
name:<br />
<%= f.text_field :name %>
</p>
<p>
link: <br />
<%= f.text_field :link %>
</p>
<p>
<%= submit_tag %>
</p>
<% end %>
It shows up fine when I go to localhost:3000/story/new. The thing is, when I try to type stuff into the form and press "submit," I get this error:
Routing Error
No route matches [POST] "/story/new"
My routes.rb looks like this:
FirstApp::Application.routes.draw do
resources :story
story_controller looks like this:
def new
#story = Story.new(params[:story])
if request.post?
#story.save
end
end
The story_controller stuff for new is straight out of the book. I thought I might have had a solution here, but no dice. Any help would be greatly appreciated.
I'm guessing you meant (note the at sign):
<%= form_for #story do |f| %>
That'll probably take care of your routing issue, but as John mentions, your controller action is a bit off, too. The new action should only load a dummy model and display the new.html.erb page - the saving should take place in a separate action, called create.
Hope this helps!
Edit: Minimal controller code:
class StoriesController < ApplicationController
def new
#Make a dummy story so any default fields are filled correctly...
#story = Story.new
end
def create
#story = Story.new(params[:story])
if(#story.save)
#Saved successfully; go to the index (or wherever)...
redirect_to :action => :index
else
#Validation failed; show the "new" form again...
render :action => :new
end
end
end
First off, Rails is relies on convention over configuration when using singular vs plural names. If you want to follow convention, you have to change the line in your routes.rb to resources :stories, which would generate following routes:
stories GET /stories(.:format) stories#index
POST /stories(.:format) stories#create
new_story GET /stories/new(.:format) stories#new
edit_story GET /stories/:id/edit(.:format) stories#edit
story GET /stories/:id(.:format) stories#show
PUT /stories/:id(.:format) stories#update
DELETE /stories/:id(.:format) stories#destroy
Note, that in this case you would have to rename your controller to StoriesController. However, your routes.rb has resources :story, which generates following routes:
story_index GET /story(.:format) story#index
POST /story(.:format) story#create
new_story GET /story/new(.:format) story#new
edit_story GET /story/:id/edit(.:format) story#edit
story GET /story/:id(.:format) story#show
PUT /story/:id(.:format) story#update
DELETE /story/:id(.:format) story#destroy
As you can see, indeed, there is no route for POST /story/new. I guess, the error that you are getting is triggered by following code in your controller:
if request.post?
#story.save
end
It is quite wrong, because you trying to check for POST request inside the action that is routed to by GET. Just remove this code from your new action and add create action to your StoryController like this:
def create
#story = params[:story]
if #story.save
redirect_to #story, notice: "Story created"
else
render action: "new"
end
end
This should resolve your issue for now. But I strongly recommend using plural stories for your resources, since it will be back to haunt you again.
This is the part that you (and me) have missed from the guide:
There's one problem with this form though. If you inspect the HTML
that is generated, by viewing the source of the page, you will see
that the action attribute for the form is pointing at /articles/new.
This is a problem because this route goes to the very page that you're
on right at the moment, and that route should only be used to display
the form for a new article.
The form needs to use a different URL in order to go somewhere else.
This can be done quite simply with the :url option of form_for.
Typically in Rails, the action that is used for new form submissions
like this is called "create", and so the form should be pointed to
that action.
Edit the form_for line inside app/views/articles/new.html.erb to look like this:
<%= form_for :story, url: stories_path do |f| %>

Multiple Contact Forms - Rails 3

I am relative newbie to all this, so sorry if this sounds mad!
I have used this tutorial: http://www.railsmine.net/2010/03/rails-3-action-mailer-example.html
And I have a new contact form working great.
The controller is at app/controllers/support_controller.rb
class SupportsController < ApplicationController
def new
# id is required to deal with form
#support = Support.new(:id => 1)
end
def create
#support = Support.new(params[:support])
if #support.save
redirect_to('/', :notice => "Support was successfully sent.")
else
flash[:alert] = "You must fill all fields."
render 'new'
end
end
end
And the model at /app/models/support.rb
class Support
include ActiveModel::Validations
validates_presence_of :email, :sender_name, :support_type, :content
# to deal with form, you must have an id attribute
attr_accessor :id, :email, :sender_name, :support_type, :content
def initialize(attributes = {})
attributes.each do |key, value|
self.send("#{key}=", value)
end
#attributes = attributes
end
def read_attribute_for_validation(key)
#attributes[key]
end
def to_key
end
def save
if self.valid?
Notifier.support_notification(self).deliver!
return true
end
return false
end
end
The views however only work in views/supports/new.html.rb (rendered - views/supports/_form.html.erb)
So I can call the Model / Controller from localhost:3000/support/new but if I try and render the same form in another view from the root directory e.g. app/view/contact.html.erb I get:
undefined method `model_name' for NilClass:Class
I think this is because it is calling the support model away from the supports directory.
Do I have to create an instance on #support so it can be called? If so what is the best way of doing that? I think I am nearly there. I just want the contact form on multiple pages not just in suppport/new
Thanks
Charlie
Yes, you would need to create a #support variable in each action you wish to render your form.
Another option would be to refactor the form to take a parameter, that way you're a bit more flexible. For example, from your view:
<%= render :partial => "supports/form", :locals => {:support => #support} %>
Now, instead of referring to #support in your _form.html.erb, you'd refer to simply support as it's a local_assign.
Yet another option would be to refactor the form a little further, and worry about creating the actual form tag outside of the partial.
Such as:
app/views/supports/new.html.erb
<%= form_for #support do |form| %>
<%= render :partial => "suppports/form", :object => form %>
<% end %>
app/views/supports/_form.html.erb
<%= form.text_field :foo %>
<%= form.text_field :bar %>
...
In this case, when you render a partial with the object option, you will get a local variable in your partial with the same name as the partial. You maintain a little bit more flexibility in the path of your form, but can still render the meat of what a Support object is inside of the form while remaining consistent across your app.
To clarify, you could use this somewhere else by doing something like:
app/views/foos/_create_foo_support.html.erb
<%= form_for #foo.support do |form| %>
<%= render :partial => "supports/form", :object => form %>
<% end %>
You have to pass #support object wherever you use your contact form. It's working in SupportsController#new because you initialize the variable there. In all other places where you want to use the form, you'll have to do the same.

Resources