Polymorphic Routes with Custom Actions in Rails - ruby-on-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 %>

Related

retrieving the :id of a given show view

I want to add an RSVP button to an Event view. For the RSVP controller, how do I retrieve the id of the Event that the view of which the user has opened, with a check that it is indeed an Event view? Should I create a private method like current_event in the application controller?
class RsvpsController < ApplicationController
def create
#rsvp = Rsvp.new(rsvp_params)
##rsvp.event_id = ???
end
end
UPDATE with additional info
my models looks like this
class Rsvp < ApplicationRecord
belongs_to :event
belongs_to :user
end
class Event < ApplicationRecord
has_many :rsvps
has_many :users, through: :rsvps
end
class User < ApplicationRecord
has_many :rsvps
has_many :events, through: :rsvps
end
You really haven't provided enough information about your application. How does an event relate to a RSVP. I'm going to make a few assumptions, but think that it will help you overall. Let's say your application will have many Events. An Event has many RSVP and a RSVP belongs to an event.
Typically, you will have something like this within your routes
Rails.application.routes.draw do
resources :events do
resources :rsvps
end
end
If you run rake routes, you may see something like this
Prefix Verb URI Pattern Controller#Action
event_rsvps GET /events/:event_id/rsvps(.:format) rsvps#index
POST /events/:event_id/rsvps(.:format) rsvps#create
new_event_rsvp GET /events/:event_id/rsvps/new(.:format) rsvps#new
edit_event_rsvp GET /events/:event_id/rsvps/:id/edit(.:format) rsvps#edit
event_rsvp GET /events/:event_id/rsvps/:id(.:format) rsvps#show
PATCH /events/:event_id/rsvps/:id(.:format) rsvps#update
PUT /events/:event_id/rsvps/:id(.:format) rsvps#update
DELETE /events/:event_id/rsvps/:id(.:format) rsvps#destroy
So your button should point to the rsvps#new controller and action. Once your user submits the form, it will call the rsvps controller and the create action.
From here, you can access your event with something like #event = Event.find(params[:event_id])
Since an event has_many :rsvps and a rsvp belongs_to :event, you can create your rsvp with something like
def create
#event = Event.find(params[:event_id])
#rsvp = #event.rsvps.create(rsvp_params) # strong params
if #rsvp.save
...
else
...
end
end
Bonus tip: Within your RSVP's form, you will need to change it to something like this since we have nested RSVP under Events.
<%= form_for [#event, #rsvp] do |f| %>
...
<% end %>

How to Create Basket of Shop site model in rails 4?

My project is about an online shopping site, using Ruby on Rails to buy phones.
My Database is User, Product, Phone.
I'm trying to create Basket model.
My route:
resources :products do
resources :phone do
resources :baskets
end
end
And my Code is:
class User < ActiveRecord::Base
has_many :baskets
end
class Phone < ActiveRecord::Base
belongs_to :product
has_many :baskets
end
class Basket < ActiveRecord::Base
belongs_to :user
belongs_to :phone
end
When i in the Show action of Product,it Show name of Product and index Phones in this Product,i want to add 1 Phone to Basket,the error is :
No route matches {:action=>"new", :controller=>"baskets", :id=>"38", :product_id=>"30"} missing required keys: [:phone_id]
I think the problem is :
http://localhost:3000/products/30/phone/38
It's Product_id = 30,but not Phone_id = 30,in here just is Id = 30.
Someone could help me fix it !
resources :products do
resources :phone do
resources :baskets
end
end
means you have to have route like this:
/products/:product_id/phones/:phone_id/baskets/:basket_id(.:format)
Which means, that in link_to you should pass the phone_id as well:
link_to 'show basket' product_phone_basket_path(product_id: #product.id, phone_id: #phone.id, basket_id: #basket.id)
link_to 'New basket' new_product_phone_basket_path(product_id: #product.id, phone_id: #phone.id)
Regardless of whether you got it working (I upvoted #Andrey's answer), you'll want to consult your routing structure.
Resources should never be nested more than 1 level deep. docs
--
In your case, I am curious as to why you have phones nested inside products. Surely a phone is a product?
Further, why are you including resources :baskets? Surely the basket functionality has nothing to do with whether you're adding a product, phone, or anything else?
I would personally do the following:
resources :products, only: [:index, :show] do
resources :basket, path:"", module: :products, only: [:create, :destroy] #-> url.com/products/:product_id/
end
#app/controllers/products/basket_controller.rb
class Products::BasketController < ApplicationController
before_action :set_product
def create
# add to cart
end
def destroy
# remove from cart
end
private
def set_product
#product = Product.find params[:product_id]
end
end
I've implemented a cart (based on sessions) before (here).
I can give you the code if you want; I won't put it here unless you want it. It's based on this Railscast.

Nested Resource Creation in Parent Form

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!!

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 %>

one-to-one relation in Rails

I'm trying to achieve a simple 1 to 1 relation in Rails 3 where a user can connect a bank account.
class User < ActiveRecord::Base
has_one :bank
accepts_nested_attributes_for :bank
attr_accessible :bank_attributes
end
class Bank < ActiveRecord::Base
belongs_to :user
end
Route
resources :users do
resources :bank
Now when i build a new bank object for a user in users/1/bank/new like this:
def new
#user = User.find(current_user.id)
#bank = #user.build_bank
end
I get an error on my for which looks like this:
<%= simple_form_for(#bank) do |f| %>
The error is:
undefined method `banks_path' for #<#<Class:0x007fa7bd090f08>:0x007fa7c0545b40>
My goal is to have a separate form for a user to add there bank account information.. Hope someone can help me in the right direction to do this. I also use ActiveAdmin and the relation with forms etc works fine there.
Any help is appreciated!
Since bank is nested under user, you need to give the user to the form:
<%= simple_form_for([#user, #bank]) do |f| %>
In addition, your routes file should be
resources :users do
resource :bank
This will give you a user_bank_path for a user
You need to declare resource in plural form regardless of the association type.
So, your resource declaration
resources :users do
resource :banks
end

Resources