Rails render forms in same page - ruby-on-rails

This is my scenario
I have a Stories controller that has a form to create a new Post :
stories.show.erb
...
<%=link_to("Create new post",new_story_post_path(#post))%>
This new post is generated by Posts controller
posts\new.erb
<%= form_for [#story,#post] do |f|%>
I want to break away from the traditional behavior - I want the new post form to be displayed inside the same 'story' page and NOT have a full page reload and appearing in a new page .
What's the best way to do this in rails ?

I think in your case it'll be best if you add the form in your stories show view and make it as hidden and then when a user clicks on the link to create a new post you can show them your form.
def show
#story = Story.find(params[:id])
#post = #story.posts.build # assuming stories have many posts
end
#show.html.erb
...
<%=link_to "Create a new Post", "#", id: "post-link"%>
<%= form_for [#story,#post], html: {id: "new-post"} do |f|%>
// form fields
<% end %>
#some_file.js
$(document).on("click","#post-link",function(){
$("#new-post").show();
});
#some.css.scss
#new-post{display:none;}

you could write:
<%= form_for [#story,#post], remote: true do |f|%>
in create.js.erb you could write
$('#post_block').html("<%= render 'your partial with posts' %>")

Related

Rails 5: Send form via AJAX, modifying inputs first, and append a view to the form

(This question has been written to focus only on what is relevant.)
Within a page view I have a Rails form:
<%= form_for #model, url: {controller: 'foo', action: 'bar'} do |f| %>
<!-- various inputs -->
<%= f.button "Submit", type: 'submit' %>
<% end %>
When I click the 'Submit' button, I would like to:
Modify some of the inputs before sending
Send the form via AJAX
Replace the form with the view corresponding to the controller action (/views/foo/bar.html.erb)
What's the cleanest way to do this please?
1. Enable form submission via ajax using remote: true option
<%= form_for #model, url: {controller: 'foo', action: 'bar'}, remote: true do |f| %>
2. Modify inputs before form submit
$('form.model_name').on('ajax:beforeSend', function(event, xhr, settings) {
//Modify inputs
});
3. Replace the form with the view corresponding to the controller action (/views/foo/bar.html.erb)
In the action where the form is handled:
def create
....
.....
respond_to do |format|
format.js
end
end
4. create a .js.erb template where the other templates for this controller are placed
create.js
$('form.model_name').html("<%= j(render 'foo/bar.html.erb') %>")`
And that's it!

How to update an existing record?

I have a Books model and it has CRUD operations. In config/routes.rb, I have declared
map.resources :books
My new.html.erb looks like as:
<%= form_for :book, url: books_path do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.submit :Add %>
<% end %>
My create method in controller looks like as:
def create
book = Book.new(authorized_params)
book.save
end
So, when I submit my form from views, request would go to the 'create' method and an record for the book gets created in database. Fair enough. Now, I want to have an edit page for book. So, my edit method in controller look like as:
def edit
#book = Book.find_by(params[:id])
render :new
end
When I go to my edit view, it automatically show the value of title in the text box, which is what I expected.. But when I try to submit the form again(ofcourse after changing the title value) it again creates a new record instead of updating it..
Something basic which I missed out in my reading? I googled about it though but did not find satisfactory answer.
The issue is that you are using the 'new' view where form has the post method. If you will check the generated routes, post will be for create method, that is adding a new record. You will have to create a new view for edit where the form target URL will be edit_book_path(#book) and method will be patch. Patch method will route to 'update' function in your controller where you will call #book.update. I am not writing the exact code, but these directions should help you achieve what you want

Displaying data from two models in one view

I have two models with respective controllers and views: Profile and Comment.
The entire view (whole webpage) of my application is in the Profile show.html.erb. On this page, the user should be able to create a comment, which belongs_to a Profile.
How can this be accomplished without having to navigate to the standard /comments/new page?
Edit:
After following the rails guide, I implemented:
<%= simple_form_for([#profile, #profile.comment.build], html: {class: "form-inline"}) do |f| %>
<%= f.error_notification %>
<%= f.input :description, label: false, placeholder: 'Create an comment', input_html: { class: "span4" } %>
<%= f.submit 'Submit', class: 'btn btn-small'%>
<% end %>
CommentController
def create
#profile = profile.find(params[:profile_id])
#comment = #profile.comments.create(params[:comment])
redirect_to profile_path(#profile)
And I'm getting this error:
undefined method `comment' for #<Profile:
Fixed: In the build statement, comments needed to be plural
#profile.comments.build
All you need to do is add the comment form code into profile#show. Then in the show action of profile_controller do something like:
def show
#comment = Comment.new
end
And in the comments controller add:
def create
#comment = Comment.create(params[:comment])
end
You might consider saving the form and updating the page using AJAX calls and possibly something like Knockout. So in profiles/show.html.erb, make a regular (separate) form just for posting comments. Use jQuery or something like it to post the form via AJAX to /comments, so it goes the create action in your comments controller. Have that controller return a JSON response, that will either be the saved comment, or a hash of error messages that looks something like {:fieldname => 'too long'}.
On the client, parse the json response and either display the saved comment, or display the error message explaining why it couldn't be saved. You can do all this in plain jQuery, but adding something like Knockout will make it all a bit simpler.

How can I change forms dynamically?

I have 2 different models called Parent and Child.
I have a page called House that has the link_to to both forms on it:
pages/house.html.erb
link_to "Parent", new_parent_path
link_to "Child", new_child_path
When you start on the page it is empty with no forms.
I want to be able to click the new_parent link and generate the parent form on the same page but if I click the new_child link I want to remove the parent form and replace it with the child form by AJAX. How would I do this?
I think it will be easier to do it without Ajax, but with css. Generate the 2 forms in the page (using partials for example), and put than inside an hidden div (css style: display: none), and use javascript with your links to show/hide the div containing the form you want to display (with JQuery show and hide effects)
This was actually very simple to create. This question and this one helped me understand AJAX and jquery better.
Lets use the parent as an example:
I put the format.js in my controller for the new action:
ParentsController
def new
#parent = Parent.new
respond_to do |format|
format.js
end
end
I delete my parents/new.html.erb as a personal choice and replace it with new.js.erb that will be called by my "Parent" link_to.
# parents/new.js.erb
$("#generate-form").html("<%= escape_javascript(render(:partial => 'parents/form', locals: { parent: #parent })) %>");
Then create a Parent form in a partial that is remote => true for AJAX.
# parents/_form.html.erb
<%= form_for(#parent, :remote => true) do |f| %>
<%= f.input :full_name %>
<%= f.button :submit, 'Done' %>
<% end %>
Then I have one page that holds both forms.
PagesController
def index
respond_to do |format|
format.html
end
end
Then the links on that page for both parent and child.
#pages/index.html.erb
<li><%= link_to "Parent", new_parent_path, :remote => true %></li>
<li><%= link_to "Child", new_child_path, :remote => true %></li>
<div id="generate-form">
</div>
The remote => true of my links and form will respond to the js format of my new action and my div id="generate-form" acts as the boss, placing either form partial inside of it. This automatically replaces one with the other depending on the link you click, thus resulting in changing forms dynamically.
Note: The code for the child model is the same (except for form fields). Just replace the Model name (Parent to Child).

embeddable component in views or how to call a different controller

Let's say you have a form that has its own controller. Is there any way to embed this form in different views (governed by other controllers)? As far as I understand partial templates carry only logic in the Ruby code that is inside the template. I am thinking more of a full-blown component where maybe you can call its controller.
The form is not driven that directly by the controllers. Yeah this is the price of all this magic.
To clarify a bit:
You type in your browser http://yourhost/posts
Your request (GET /posts) hits the router, then your router says that the /post urls belongs to the PostsController and the action is index
Then your controller executes the index method, do your business logic (loads the posts from the database, for example)
loads the view (views/posts/index...) and run it by 'substituting' all the instance variables and stuff defined in your controller (eg #posts = Post.all) that you have in it
then you see the view rendered with a list of posts (if in the view you have something similar to #posts.map{|p| p.title}.join(", ") )
yes I know it's not the best /posts view in the world but it's only to grasp the idea
The same goes for form, your form tag (for example form_for) gets an instance from the controller (let's say #post) and (in edit mode) gets filled with your Post attributes.
Then when you (edit something and) click the submit button it makes a request (by default a PUT to /posts) passing all the values in the form, then your controller gets the (POST) values of the requests (the ones you see in the server log) and makes his work (like saving the post's datas)
and because of this in a controller you can use the method
render :controller => :foo, :action => :bar
to render another controller action different from the default one
Hope this will be useful!
You can create a form in any view to call any controller. In a RESTful app, you can usually just pass an empty object (using the Posts/Commments example from makevoid)
<% form_for #new_comment do |f| %>
<%= f.text_area :text %>
<%= f.submit %>
<% end %>
This form should route to the create action on CommentsController. From there, you could use redirect_to :back in order to get back to the view that triggered this controller. This does have some validation issues I think though.
If you are non-RESTful, you can use the old form_for style:
<% form_for :comment, #new_comment, :url => { :controller => "comments", :action => "create" } do |f| %>
<%= f.text_area :text %>
<%= f.submit %>
<% end %>
For either of these examples, you need to have the #new_comment, which you would create in your PostsController:
def show
#post = Posts.find(params[:id])
#new_comment = #post.comments.build
end

Resources