Creating multiple forms without flooding the controller with new objects - ruby-on-rails

How do I create multiple forms without having to create the object in the controller/load them by ajax?
Say you have a Forum model that has many posts, and each post has many tags. You are looking at a list of posts at forms_controller#index.
Say you want to allow the user to tag posts from the same template. One way is to load the form via a remote link when needed.
# forums/index.html
= link_to "Add Tag", new_post_tag_path, remote: true
# tags_controller.rb
def new
#tag = #post.tags.build
respond_to do |format|
format.js
end
end
# tags/new.js.erb
<%= j render("form") %>
But that requires going to the server and back again to render the form.
Another way is to create the object when looping over the posts.
- #posts.each do |post|
= form_for #post.tags.build do
That doesn't work very well if you want to create several tags at the same time.
Is there another way to do this, perhaps with JS, without having to go to the server/or create the object as in the second approach?

Ryan Bates has done a great screencast on this subject. http://railscasts.com/episodes/197-nested-model-form-part-2 Hopefully this should provide you with what you need.

Related

How to insert a form for a new record using Turbo/Stimuls?

The future of Rails seems to be moving away from UJS and towards Turbo and Stimulus. But, some features seem to require more code and more work vs old-school UJS.
For example: To insert a form inline that would allow the creation of a new record you could create a link like:
link_to 'add record', new_user_path, remote: true
then, in the controller:
def new
#user = User.new
respond_to do |format|
format.js
end
end
and, finally a new.js.erb view containing the content (form) to be executed in response to the new action.
One would think this would be simple to convert to Turbo simply by responding to:
def new
#user = User.new
respond_to do |format|
format.turbo_stream
end
end
and then change the new.js.erb to new.turbo_stream.erb with content like:
<turbo-stream action='append' target="<%= dom_id #user %>_new_form">
<template>
...the user form stuff...
</template>
</turbo-stream>
However, Turbo Streams do not handle GET requests. So, this approach does not work.
My question is: What is the Rails 7 way of handling this? I don't think UJS is the answer, since Turbo and Stimulus are "superseding" UJS functionality. Perhaps, the answer is to insert the new form using Stimulus, then use a stream to insert the subsequent new record following the POST request when the form is successful in the create action. But, this seems like so much more work than UJS is/was. It makes me think there's gotta be an easier, more fluid, less code way to handle this.
For this I would use turbo-frames :
First make an empty turbo-frame called new_record
Then you can have a link and set the data-turbo-frame="new_record"
this will act as if you clicked the link inside of the new_record turbo-frame
Then on the new template have a matching turbo-frame wrap your form and then
When you click on the new record button it will put the new record form in the spot I would say this is the hotwire way of doing it.

How to compare two items within Ruby on Rails?

So I'm trying to re-create GitHub version control for let's say posts. I've found a way to re-create an original post using duplicate AND another method to create a new post based on the original. Cool.
My issue is being able to display both the original and the new on the same page.
What I've attempted thus far is to just rely on the show method with having:
def show
#post = Post.find(params[:id])
end
Then in the view have in the form a checkbox to allow a user to select multiple posts, click a submit, and a new page renders displaying both side by side. Preferably showing the differences between the two but that's a wish list as I deal with this first.
Actually could I just simply do?:
def other_show
#post = Post.where(params[:id])
end
I also added in status as a boolean to help on the view for marking the checkbox. Would I then need to put something in the other_show method about the status?
If you want to "recreate" some sort of version control I suggest you use something like the audited. Instead of building your own. From your example and comments it seems you don't have a clear relation between all related (versions of) posts.
Using this gem, each change to the Post content (for example, if configured properly) would be stored as an audit.
Showing the differences is a different problem. That's usually called a diff and you can find gems that do it for you, for example: diffy
To show 2 different entities on one page you need to give posts_controller both ids.
Declare your show method like this:
def show
#original = Post.find(params[:id])
#compared = Post.find(params[:compared_id])
end
Correct route to this method will look like this:
/posts/:id?compared_id=:another_id
# Example: /posts/1?compared_id=2
To construct such a link in your view, you need to declare link_to method like this:
<%= link_to '1 <> 2', post_path(#post, compared_id: '2') %>
If you want to have a page where user can check 2 checkboxes for certain posts, you'll need to construct such href via Javascript.
But in fact I wouldn't suggest you to modify show method for such a task. It is better to use show method only for showing one entity from database. You can create another method, e.g. compare and pass both parameters there.
def compare
#original = Post.find(params[:original_id])
#compared = Post.find(params[:compared_id])
end
In routes.rb
resources :posts do
get 'compare', on: :collection
end
It will give you helper compare_posts_path, which will lead to /posts/compare and you'll need to pass original_id and compared_id to it, like this:
<%= link_to 'Compare', compare_posts_path(original_id: 'some_id', compared_id: 'some_another_id') %>
It will result to
/posts/compare?original_id=some_id&compared_id=some_another_id

how does devise current_user work?

I want to understand how the devise current_user method works because I want to generalize it to other models which would allow code such as current_forum or current_forum_thread.
To be more specific I am trying to implement a chat forum in Rails. I have a page showing all posts (currently none) for a specific discussion thread. That same page has the new post form embedded. The debug(params) shows:
action: show
controller: discussions
forum_id: '1'
id: '1'
discussion: !ruby/object:Discussion
attributes:
id: 1
title: first discussion (thread)
forum_id: 1
So the create method in the posts controller should know what the discussion id is. Yet this code in the controller does not work.
1. #discussion = Discussion.find(params[:id])
2. #post = #discussion.posts.new(params[:post])
3. if #post.save
4. flash[:success] = "Discussion post created!"
5. redirect_to '#'
6. else
7. render '#'
8. end
Line 1. raises the error:
Couldn't find Discussion without an ID
Also, on inspection it turns out that the #discussion variable is always NIL.
I think it's more of a helper function, devise's way is to get the ID through the session, but you could do the same through the params hash,
i.e
module ApplicationHelper
def current_forum_thread
Thread.find(params[:id])
end
end
does that work for you?
I'm putting before_filter :authenticate_user! on top of every controller, and then do something like this:
current_user.posts.new(params)
This also requires relation User has_many :posts
Seems to work (not sure if that's the best way though).
Also, your error seems to mean that your prarms[:id] is nil, so check if it's passing properly. You should be able to see that in the logs.
# Discussions controller - show action
#discussion = Discussion.find(params[:id])
render ...
# Discussion show view
%ul
- #discussion.posts.each do |post|
%li= post.content # to output list of posts
= form_for #discussion.posts.new do |f|
= f.input :content
= f.submit
# form to create new post related to this discussion
# Post controller - create method
#post = Post.new(params[:id])
#post.save!
render ...
Using the current_id with threads seems overly complex for this implementation, as it looks like a pretty simple nested resource.
The post isn't saving, because it couldn't find the discussion. Since you're on the Post controller and not Discussion, you need to be looking for the discussion with
#discussion = Discussion.find(params[:discussion_id])
The :id your searching for it with is from the post's parameters. It didn't find anything because you probably have significantly more posts that discussions. If it did find something it would be finding the wrong thing.
Another things on the check list to make a nested route work, is to get the routes right. Check them with 'rake routes' but it should look like this:
resources #discussions do
resources #posts
end
This will add the routes so your form, which should look something like <%= form_for [#discussion, #post] do |f| %> can post/put to the discussion_posts_path.
Using the current_id like you got into is really scoping, and it's kind of messy, Ryan Bate's has an awesome video on Multi-tenancy with scopes http://railscasts.com/episodes/388-multitenancy-with-scopes

Rails - ability to link directly to views that are rendered via ajax

Looking for the best way to implement this. Currently I have a "show" page for Users - that shows all of a users' pictures.
def show
#user = User.find(params[:id])
#pictrues = #user.pictures
end
On that page, I have various tabs. When a user clicks on one of those tabs, an ajax call renders a view... particularly, it updates a partial that was previously showing all of a users pictures (and an additional partial for statistics). For the "show comments" example, it updates the partial with all of the pictures a user has commented on:
def show_comments
#user = User.find(params[:id])
#pictures = #user.picture_comments.map{ |p| p.picture }.uniq
respond_to do |format|
format.html
format.js
end
end
The show_comments.js.erb file looks like:
$("#user_content_container").html("<%= escape_javascript render(partial: 'shared/pictures', pictures: #pictures) %>");
$("div.user_header").html("<h4>Comments</h4><br/>");
$("#stat_container").html("<%= escape_javascript render(partial: 'shared/comment_stats', pictures: #pictures) %>");
What I want to do, is to keep the current functionality of the page. But also be able to link directly to the views that are rendered via ajax. For example, have a link on another page that goes directly to the users "show" page, as it is when the "comments" tab is clicked on.
I have a few ideas, but an not sure what the "cleanest" way of doing this would be. Let me know if you need any additional clarification, b/c I'm honestly having as difficult time wording this question, as I am in finding the best way to implement this!
This sounds like something that you might be able to solve using turbolinks. If you can update your app to use Rails 4, you get this bundled in. Otherwise, you can use the gem. For more information on how to do this, watch the railscast on turbolinks.
If you don't want to or can't use them, you could also try passing params in the url, and check for them when the page is loaded. You could use the params to modify the page in the same way that it would be modified after the AJAX call.

What is the right way of creating a nested view in rails

I have blog model. Blog has_many comments. I have created all the CRUD related to the blog. The comments doesnt have a page on its own. On the blog page, there could be text area and on the entering the comment, it would be saved thru ajax. But normally when a new page is created a new object is sent from the controller, so should i create a comment object and send it thru Blog's new action like this
def new
#comment = Comment.new
#blog = Blog.new
end
Or should i just access the comment objects present in the blog while creating the view
<form_remote_for #blog.comments>
Which is the right way of doing this? Is there any better solution
Its preferred to have initialization of new comment in controller action. But its rather a guideline or practice I follow rather than the rule.
There is no form_remote_for tag. If its rails 2, tag is remote_form_for, similar thing in rails 3 would be:
form_for [#blog, #comment], :remote => true do |f|

Resources