Nested Resource Creation in Parent Form - ruby-on-rails

I have a class called Quote which has_many :line_items, as: :line_itemable (line_items are polymorphic). A quote must have at least one line_item upon creation, so in my Quote creation form I have a section dedicated to adding line items. My routes look like this:
resources :quotes, shallow: true do
resources :line_items
end
which means my routes look like this:
POST /quotes/:quote_id/line_items(.:format) line_items#create
new_quote_line_item GET /quotes/:quote_id/line_items/new(.:format) line_items#new
In the line items section of the quote form I have a button that, when clicked, links to the new_quote_line_item controller action to render a line_item creation modal. My issue is that since the quote hasn't been created yet it doesn't have :quote_id to use in the path. How can I go about achieving this the Rails Way™? I was considering using ajax but I'm not sure if that is overkill for this situation. Thanks for your help!

You should user accepts_nested_attributes_for method in your model to accept attributes for LineItem and fields_for helper
Your model should looks like:
class Quote < ActiveRecord::Base
accepts_nested_attributes_for :line_item
...
end
And you template like:
form_for #quote do |f|
f.fields_for :line_items do |f2|
...
end
...
end

Ajax
You wouldn't need ajax functionality for this - Ajax only allows you to pull data from the server asynchronously, which essentially means you don't have to reload the page.
--
Nested Attributes
What you're looking for, as alluded to by atomAltera sounds like accepts_nested_attributes_for - which allows you to create dependent models from the parent
It sounds to me that you'll need to create a quote before you try and populate line_items, which is actually quite simple using ActiveRecord:
#app/models/quote.rb
Class Quote < ActiveRecord::Base
has_many :line_items
accepts_nested_attributes_for :line_items
end
#app/controllers/quotes_controller.rb
Class QuotesController < ApplicationController
def new
#quote = Quote.new
#quote.line_items.build
end
def create
#quote = Quote.new(quote_params)
#quote.save
end
private
def quote_params
params.require(:quote).permit(:quote, :attributes, :new, line_items_attributes: [:line, :items, :attributes])
end
end
--
If you need any further information, please let me know!!

Related

Polymorphic Routes with Custom Actions in Rails

So I am making a chat app, and I want users to be able to leave chat rooms. This would be done by posting a delete request to a route like /users/:id/chat_rooms/leave/:chat_room_id.
The Users model has has_many :chat_rooms, through: chat_room_users while ChatRooms has has_many :users, through chat_room_users. The UsersController has a leave action, which I want to call using this request on this url.
I want to create a link to this url on a view that I have. I already have a variable #user for the current user and #chosen for the current chat room available on the view. So how would I do a link_to and route for this setup? I have delete /users/:id/chat_rooms/leave/:chat_room_id in the routes.rb file, but how would I do the link_to?
Thank you.
You're overcomplicating it.
DELETE /chat_rooms/:chat_room_id/leave
Instead of passing the user id via the URL you should instead get it through the session or a token (if its an API app).
Rule of thumb: resources should never be nested more than 1 level
deep. A collection may need to be scoped by its parent, but a specific
member can always be accessed directly by an id, and shouldn’t need
scoping (unless the id is not unique, for some reason).
http://weblog.jamisbuck.org/2007/2/5/nesting-resources
This is just a loose example of how to solve this:
# routes.rb
resources :chat_rooms do
member do
post :join
delete :leave
end
end
class User
has_many :chat_room_users
has_many :chat_rooms, though: :chats
end
class ChatRoomUser
belongs_to :user
belongs_to :chatroom
end
class ChatRoom
has_many :chat_room_users
has_many :users, though: :chats
end
Putting this in UsersController is pretty questionable. I would instead place it in ChatroomsController.
class ChatroomsController
# ...
# POST /chat_rooms/:chat_room_id/join
def join
#chat_room = ChatRoom.find(params[:id])
#chat = current_user.chat_room_users.new(chat_room: #chat_room)
if #chat_room.create
# ...
else
# ...
end
end
# DELETE /chat_rooms/:chat_room_id/leave
def leave
#chat_room = ChatRoom.find(params[:id])
#chat = current_user.chat_room_users.find_by(chat_room: #chat_room)
#chat.destroy
end
end
<%= button_to 'Join', join_chat_room_path(#chat_room), method: :post %>
<%= button_to 'Leave', leave_chat_room_path(#chat_room), method: :delete %>

Nested routes and CRUD operations with additional values for assciation in Rails

I created one has_many through relationship in rails api. I also used nested routes.
My models like below;
class Author < ActiveRecord::Base
has_many :comments
has_many :posts, :through => :comments
end
class Post < ActiveRecord::Base
has_many :comments
has_many :authors, :through => :comments
end
class Comment < ActiveRecord::Base
belongs_to :author
belongs_to :post
end
my routes like below;
Rails.application.routes.draw do
namespace :api, defaults: { :format => 'json' } do
resources :posts do
resources :comments
end
resources :authors
end
end
So here my aims are
Comments are nested route so that i can create and display comments from post
Here not any post author. The author is meant for comment owner
I implemented the concepts and working almost all. But i am facing the following 2 problems with associations
How to add additional fields for associated table when parent create. Here my requirement is when a post is created, i need to insert one default entry for comment. My post controller implementation for create is like below
def create
params = create_params.merge(:record_status => 1)
#post = Post.new(params)
#post.authors << Author.find(1)
if #post.save
render :show, status: :created
end
end
def show
#post = Post.find(params[:id])
end
private
def create_params
params.permit(:name, :description, :author_id )
end
Here i am passing author_id in the request json. This needs to be added as author id in the comments table. Now i just hard coded as 1. I used '<<' for association entry. This is working but i also need to include two more fields which are :comments and :record_status. Again :comments is from the request itself.
Note: This is not rails mvc application. This is rails api and input as json.
When i display comments using nested routes i need to show author and also comments from comments table. My comments controller method is ;
class Api::CommentsController < ApplicationController
before_filter :fetch_post
def index
#authors = #post.authors.where(:record_status => 1, comments: { record_status: 1 })
end
private
def fetch_post
#post = Post.find(params[:post_id])
end
end
Here i got authors but not correct comments in the join table 'comments'
Please help me to solve these issues
For the first problem, you want to configure the posts_controller to accept the nested attributes for comments. Add this line at the beginning of your controller:
accepts_nested_attributes_for :comments
Check the documentation for further details on this method.
Then, you need to modify the parameters that the controller will allow:
def create_params
params.permit(:name, :description, :author_id, comments_attributes: [ :author_id, :comments, :record_status ] )
end
Modify the attributes listed in the comments_attributes array to match those of your Comment model.
I'm not sure what you're trying to get in the second problem, but maybe you just need to query a little differently:
#comments = Comment.where(post_id: #post.id).includes(:author)
The above would return a list of comments including the comment author.

Rails many to many model, adding records

Hello im trying to add some records to my database with this model
class Colleagueship < ActiveRecord::Base
belongs_to :employee
belongs_to :colleague, :class_name => 'Employee'
end
class Employee < ActiveRecord::Base
has_many :colleagueships
has_many :colleagues, :through => :colleagueships
# ...
end
but i have no idea in how to start a new form to create new records
im thinking to try something like
def new
employee = ## gotta get the id here in the form
#colleagueship = employee.colleagueships.build(:colleague_id => params[:colleague_id])
#colleagueship.save
end
what do you think? how do i achieve this with a post http method? do i have to save the employee variable with the request and add the employee_id there?
In the controller
def new
end
def create
# inspect submitted params here
puts params
if colleagueship.save
# etc etc
else
# error
end
end
private
def employee
#employee = Employee.find_by(params[:employee_id])
end
def colleagueship
#colleagueship = employee.colleageships.build
end
helper_method :employee, :colleagueship
Your routes should be nested to provide the key you'll use to find the employee.
resources :employees do
# this will generate /employees/:employee_id/colleagues/:id
resources :colleagueships
end
In your view, you will probably use the form_tag helper, as it's easier to customize forms with whatever fields you want, especially if you're avoiding accepts_nested_attributes which you should. You can also include a hidden_field_tag with employee_id if you aren't nested your routes.
= form_tag new_employee_colleague_path do
= text_field_tag 'colleageship[name]', placeholder: 'something...'
Something along these lines should work. Make sure to inspect the params hash to see that the values are formatted correctly.

Adding Posts To A Collection with Join Models

I'm trying to add a 'Collections' model to group Posts so that any user can add any Post they like to any Collection they've created. The Posts will have already been created by a different user. We are just letting other users group these posts in their own Collections. Basically like bookmarking.
What is the cleanest, and most Rails-ey-way of doing this?
I've created the model and run through the migration and what not. Also I've already created proper views for Collection.
rails g model Collection title:string user_id:integer
collections_controller.rb
class CollectionsController < ApplicationController
def index
#collections = current_user.collections.all
end
def show
#collection = Collection.all
end
def new
#collection = Collection.new
end
def create
#collection = current_user.collections.build(collection_params)
if #collection.save
redirect_to #collection, notice: 'saved'
else
render action: 'new'
end
end
def update
end
private
def collection_params
params.require(:collection).permit(:title)
end
end
collection.rb
class Collection < ActiveRecord::Base
belongs_to :user
has_many :posts
validates :title, presence: true
end
post.rb
has_many :collections
It seems like has_many or has_and_belongs_to_many associations are not correct? Should I be creating another model to act as an intermediary to then use
has_many :collections :through :collectionList?
If my association is wrong, can you explain what I need to change to make this work?
Also the next part in this is since this is not being created when the Post or Collection is created, I'm not sure the best way to handle this in the view. What is the best way to handle this, keeping my view/controller as clean as possible? I just want to be able to have a button on the Post#Show page that when clicked, allows users to add that post to a Collection of their own.
In such case you should use or has_and_belongs_to_many or has_many :through association. The second one is recommended, because it allows more flexibility. So now you should:
Create new model PostsCollections
rails g model PostsCollections post_id:integer collection_id:integer
and migrate it
Set correct model associations:
Something like:
class Post < ActiveRecord::Base
has_many :posts_collections
has_many :categories, through: :posts_collections
end
class Collection < ActiveRecord::Base
has_many :posts_collections
has_many :posts, through: :posts_collections
end
class PostsCollections < ActiveRecord::Base
belongs_to :post
belongs_to :collection
end
Then you'll be able to use
#collection.first.posts << #post
And it will add #post to #collection's posts
To add a post to a collection from view
Add a new route to your routes.rb, something like:
resources :collections do # you should have this part already
post :add_post, on: :member
end
In your Collections controller add:
def add_post
#post = Post.find(params[:post_id])
#collection = Collection.find(params[:id])
#collection.posts << #post
respond_to do |format|
format.js
end
end
As for views, you'll have to create a form to show a collection select and button to add it. That form should make POST method request to add_post_collection_path(#collection) with :post_id parameter.
You can read more explanations of how rails associations work in Michael Hartl's tutorial, because that subject is very wide, and can't be explained with short answer.

Where to put nested resources delete for many to many relationship

I have Rails 4 app with nested resources and the child(session) also has a many-to-many relationship with another model (speakers).
resources :parent do
resources :child
end
class Parent < ActiveRecord::Base
has_many :children
end
class Child < ActiveRecord::Base
belongs_to :parent
has_and_belongs_to_many :speakers
end
class Speaker < ActiveRecord::Base
has_and_belongs_to_many :children
end
I am trying to figure out which controller should have the delete/add for the relationship(speaker). I could handle this in the SessionController#destroy but would have to handle a special case for this relationship(doesn't feel right). Currently I have a custom route to SessionController#speaker passing in a param of operation(:add, :delete).
I am keeping the records on both sides of the relationship, only remove the relationship
sessions.speakers.delete(speaker)
What do you think are the best approaches for this?
Keep the remove relationship in SessionController in a special route
Add to SpeakerController in a special route
Create a new controller to handle the relationship
ActiveRecord
To add or remove objects from a collection, you have the << and .delete ActiveRecord methods
These work using ActiveRecord objects, and can be called like this:
#apps/controller/posts_controller.rb
def comment
post = Post.find(params[:post_id])
comment = Comment.find(params[:comment_id])
#Add
post.comments << comment
#Delete
post.comments.delete(comment)
end
Controller
In response to your question as to which controller, I'd recommend keeping the code in the "parent" controller (in your case, sessions) & creating a single method to handle the process
Here's live code we created last week:
#config/routes.rb
resources :entries do
match ":category_id", to: :category, via: [:post, :delete], as: "category"
end
#app/controllers/entries_controller.rb
def category
entry = Entry.find(params[:entry_id])
category = Category.find(params[:category_id])
#Actions
entry.categories << category if request.post?
entry.categories.delete(category) if request.delete?
#Return
redirect_to collection_path
end
This allows us to call a single link with different :method to call different actions:
<%= link_to "Add Category", admin_entry_category_path("60", "20"), method: :post %>

Resources