How can I implement a 'create' action without 'new' action - ruby-on-rails

How can I best implement this feature: As an admin, I can assign a Resident Manager to a Hall.
I have a User model with a namespace routing for the admin -I intend on having another namespace routing that would hold the functions of the RM-
I have a Hall model.
Since its a many-many relationship between the above to models, I have a Management join model which contains only user_id and hall_id columns.
I know implementing the above feature, entails creating a new record in the management table but I don't know how to do it. I didn't think using a form (management#new) would solve this because the admin should not know the user_ids/hall_ids...
BELOW IS WHAT I HAVE TRIED TO DO BUT I CAN'T GET IT RIGHT
When the admin gets to the user index page, s/he should see a link for Hall Assignment for each user. This link leads to the management show page for that particular user, which would show the list of halls assigned to that user and also the show all the other remaining halls that isn't assigned to the user. So, either clicking an ADD button or on the hall's name should add it to that user's assigned halls list which is on the same page.
Management#show page
<h2><%= #user.email %>'s assigned halls</h2>
<% #user.managements.each do |management| %>
<%= management.hall.name %>
<% end %>
<p> HALL LISTS </p>
<ul>
<% #halls.each do |hall| %>
<li><%= hall.name %> <%= button_to "Add" %> </li>
<% end %>
</ul>
Here's is my Management controller
class Admin::ManagementsController < ApplicationController
def index
#managements = Management.all
end
def show
#user = User.find(params[:id])
#halls = Hall.all
end
def create
#management = Management.create(managements_params)
redirect_to admin_management_path
end
private
def managements_params
params.
require(:management).
permit(user_id: params[:user_id], hall_id: params[:hall_id])
end
end
And here's a piece of what my routes file looks like:
namespace :admin do
resources :users, only: [:index, :update]
resources :halls, only: [:index, :new, :create]
resources :managements, only: [:index, :new, :create, :show] do
resources :halls, only: [:index]
end
end

Your "add" button is just a mini form (with mostly hidden fields). You can instead just make it an actual form (with the submit-button having the text "Add") and the id-values filled in from the item on the page... it just points to the same routes that you'd normally point the form that you'd find in the new template.
if you want more detail, then show us the code that you have written (rather than a verbal description of it).
Edit:
Ok, so you'd put a button on the page like this
<ul>
<% #halls.each do |hall| %>
<li><%= hall.name %> <%= button_to "Add", managements_path(management: {user_id: #user.id, hall_id: hall.id}, method: :put ) %> </li>
<% end %>
</ul>
Notice the managements_path - you might need to check that that routing is correct (check it against what is in rake routes). Note that you're passing in the user id and the hall id, and that you must set the method to "put" on the button.

First things first -
How can I implement a 'create' action without 'new' action
It's relatively simple to do - you will need a create action somewhere, but a new action is just a way to build the respective ActiveRecord object for your controller.
If you do this in another action, you just have to make sure you point the form to the correct create action (and that create action to redirect back to
--
New / Create
Here's how you could handle the new / create actions in different controllers, as an example for you:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def index
#hall = Hall.new
end
end
#app/controllers/halls_controller.rb
Class HallsController < ApplicationController
def create
#hall = Hall.new hall_params
redirect_to users_path if #hall.save
end
private
def hall_params
params.require(:hall).(:hall, :attributes, :user_id)
end
end
This will allow you to show the following:
#app/views/users/index.html.erb
<% #users.each do |user| %>
<%= link_to user.name, user %>
<%= form_for #hall, url: hall_path do |f| %>
<%= f.hidden_field :user_id, value: user.id %>
<%= f.text_field :x %>
<%= f.submit %>
<% end %>
<% end %>
--
Fix
ADD button or on the hall's name should add it to that user's assigned halls list
For this, I don't think you'd need a create action in the "traditional" sense - it will be more about adding new halls to a user's current halls. This is much different than creating a new hall itself:
#config/routes.rb
namespace :admin do
resources :users do
post "hall/:id", to: :add_all #=> domain.com/admin/users/:user_id/hall/:id
end
end
#app/controllers/admin/users_controller.rb
Class UsersController < ApplicationController
def add_hall
#user = User.find params[:user_id]
#hall = Hall.find params[:id]
#user.halls << #hall
end
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :user_halls
has_many :halls, through: :user_halls
end
#app/models/hall.rb
Class Hall < ActiveRecord::Base
has_many :user_halls
has_many :users, through: :user_halls
end
#app/models/user_hall.rb
Class UserHall < ActiveRecord::Base
belongs_to :user
belongs_to :hall
end
This uses the ActiveRecord collection methods to make this work, to which you'll be able to provide the following:
#app/views/users/index.html.erb
<% #users.each do |user| %>
<%= link_to user.name, user %>
<%= button_to "Add Hall Test", user_add_hall_path(user, 1) %>
<% end %>

Related

Rails not directing to show page after user submit

Working with nested routes and associations. I have a partial which creates a tenant, but after the creation it stays with the form rendered and the url changes to /tenants. Desired behavior is that it needs to redirect_to the show page. Routes are as follows:
Rails.application.routes.draw do
devise_for :landlords
authenticated :landlord do
root "properties#index", as: "authenticated_root"
end
resources :tenants
resources :properties do
resources :units
end
root 'static#home'
end
So far the properties and units work (and the landlord) Issue is with Tenants. Originally I had Tenants nested under units, but had issues there as well. Partial looks like this:
<%= form_for #tenant do |f| %>
<%= f.label "Tenant Name:" %>
<%= f.text_field :name %>
<%= f.label "Move-in Date:" %>
<%= f.date_field :move_in_date %>
<%= f.label "Back Rent Amount:" %>
$<%= f.text_field :back_rent %>
<%= f.button :Submit %>
<% end %>
<%= link_to "Cancel", root_path %>
Tenants Controller looks like this:
before_action :authenticate_landlord!
#before_action :set_unit, only: [:new, :create]
before_action :set_tenant, except: [:new, :create]
def new
#tenant = Tenant.new
end
def create
#tenant = Tenant.new(tenant_params)
if #tenant.save
redirect_to(#tenant)
else
render 'new'
end
end
def show
end
def edit
end
def update
if #tenant.update(tenant_params)
redirect_to unit_tenant_path(#tenant)
else
render 'edit'
end
end
def destroy
end
private
def set_property
#property = Property.find(params[:property_id])
end
def set_unit
#unit = Unit.find(params[:unit_id])
end
def set_tenant
#tenant = Tenant.find(params[:id])
end
def tenant_params
params.require(:tenant).permit(:name, :move_in_date, :is_late, :back_rent, :unit_id)
end
end
Models have associations:
class Tenant < ApplicationRecord
belongs_to :unit, inverse_of: :tenants
end
class Unit < ApplicationRecord
belongs_to :property, inverse_of: :units
has_many :tenants, inverse_of: :unit
end
Lastly the show#tenants in rake routes is:
tenant GET /tenants/:id(.:format) tenants#show
I have extensively searched for this topic, but haven't had any success. Any help is appreciated. Rails 5.1
The route you are showing near the end of your question:
tenant GET /tenants/:id(.:format) tenants#show
is not the tenants index; it is the individual tenants/show route. You can tell this because it includes :id, which means it will show you a specific tenant having that id.
Try running rake routes again. The index route should look like this:
tenants GET /tenants(.:format) tenants#index
If you want to return to the tenants index after creating or updating a Tenant record, then you need to specify that path in your TenantsController. In both the #create and #update actions, your redirect line (after if #tenant.save and if #tenant.update, respectively) should read:
redirect_to tenants_path
That will take you to the TenantsController, #index action.
In the alternative, if you want to return to the individual tenant show page, then instead change both of those redirects in the TenantsController in both the #create and #update actions to:
redirect_to tenant_path(#tenant)
That will take you to the TenantsController, #show action for the current #tenant.

How to load all associations across all models

I'm making a simple website to learn RoR. It allows users to post reviews for movies, but I want to implement a link in my root view that shows ALL of the reviews in the database. How do I do that?
I want to be able to <%= link_to 'Reviews', reviews_path %> but my reviews#index URI pattern is /movies/:movie_id/reviews
What do I put in my reviews model in order to extract all the reviews in the database?
My reviews controller:
class ReviewsController < ApplicationController
def create
#movie = Movie.find(params[:movie_id])
#review = #movie.reviews.create(review_params)
redirect_to movies_path(#movie)
end
private
def review_params
params.require(:review).permit(:email, :comment)
end
end
Review model:
class Review < ApplicationRecord
belongs_to :movie
end
And my root view:
<h3>All Reviews:</h3>
<!-- put link here -->
<h3>Sort By:</h3>
<p>
<%= link_to 'Release', movies_path(:sort_param => "release") %>
<%= link_to 'Title', movies_path(:sort_param => "title") %>
</p>
<h1>Popular Movies</h1>
<% #movies.each do |m| %>
<h2>Title</h2>
<%= m.title %>
<%= m.release %>
<%= link_to 'Show', movie_path(m) %>
<% end %>
Edit: I looked at this solution but I'm not sure where to put that code. I tried to put it in the Review's index method but I got an error:
Couldn't find all Reviews with 'id': (all, {:order=>"created_at DESC", :limit=>10})
In your routes.rb
resources :reviews, only: [:index]
reviews_controller.rb
If you want to display all the reviews from any movie, in most recent order, you could do:
class ReviewsController < ApplicationController
def index
#reviews = Review.order('created_at DESC').limit(10)
end
def create
...
end
end
You can add a route to reviews only in your routes.rb file.
resources :reviews, only: [:index]

Routing a form for a polymorphic model's non polymorphic side

I have set up a polymorphic association between Event, Meeting, and TaskList such that:
An Event can have many TaskLists, A TaskList can belong to one Event.
A Meeting can have many TaskLists, A TaskList can belong to one Meeting.
This works and I can create task_lists from the view of these models. However my problem is that I want to be able to add TaskItem to each TaskList such that:
A TaskList can have many TaskItems, A TaskItem can belong to one TaskList.
I'm having trouble routing the form for the creation of a task item. I've created a "_form.html.erb" for this and am rendering it from the view of the task_item. I'm using the form below for this, at the moment from the events view, which shows the form fine but throws the routing error "No route matches [POST] "/events/3/task_lists/new.3" when clicked on submit.
_form.html.erb
<%= form_for TaskItem.new, url: new_polymorphic_path([#listable, #task_list, #task_item]) do |f| %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_field :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I have also tried setting it up like below which doen't even show the form throwing the error "First argument in form cannot contain nil or be empty"
<%= form_for #task_item do %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_field :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Models
class TaskList
belongs_to :listable, polymorphic: true
has_many :task_items
end
class TaskItem
belongs_to :task_list
end
class Event
has_many :task_lists, as: :listable
end
class Meeting
has_many :task_lists, as: :listable
end
Routes (added :show to task_lists, only: , as my link wouldnt work otherwise.)
concern :has_task_lists do
resources :task_lists, only: [:new, :index, :create, :show]
end
resources :events, :meetings, concerns: [:has_task_lists]
resources :task_lists, except: [:new, :index, :create] do
resources :task_items
end
task_items_controller (want it to redirect to the page item was created from, which is the show view for task_list)
def create
#task_item = #task_list.task_items.new(task_item_params)
if #task_item.save
redirect_to #task_list, notice: "Task Item Created"
else
render :new
end
end
task_lists_controller
before_action :load_listable
def show
#task_list = #listable.task_lists.find(params[:id])
end
def load_listable
klass = [Event, Meeting].detect { |c| params["#{c.name.underscore}_id"]}
#listable = klass.find(params["#{klass.name.underscore}_id"])
end
One of the key issues here is that task_items are nested a level too deep.
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.
- Jamis Buck
So to untangle this we would declare the routes like so:
concern :has_task_lists do
resources :task_lists, only: [:new, :index, :create]
end
resources :events, :meetings, concerns: [:has_task_lists]
resources :task_lists, except: [:new, :index, :create] do
resources :task_items
end
The routing concern lets us re-use the nested routes.
Since we have gotten rid of the extra "parent" we can simplify the form:
form_for([ #task_list, #task_item || #task_list.task_items.new ])
#task_item || #task_list.task_items.new will prevent errors if you embed the form in another controller / action.
Added
You actually have to create the resource scoped to the parent. Just doing form_for([#a, #b]) does not automatically associate the records - it just generates the url and sets the form method (POST or PATCH).
class TaskItemsController < ApplicationController
before_action :set_task_list
def new
#task_item = #task_list.task_items.new
end
def create
#task_item = #task_list.task_items.new(task_item_params)
if #task_item.save
redirect_to task_list_path(#task_list), notice: "Task Item Created"
else
render :new
end
end
private
def set_task_list
#task_list = TaskList.find(params[:task_list_id])
end
# ...
end
When you call #task_list.task_items.new you are calling the .new method on the collection - so the new TaskItem will have the task_list_id column set.
Note that you can also write redirect_to #task_list and rails will figure the rest out. If you need to redirect to a nested resource such as the newly created item you would do:
redirect_to [#task_list, #task_item]

Where to handle request that deal with multiple models

Example: a Survey has many Questions. A request is made to create a survey, and the request contains the survey title and metadata, plus all the questions.
This all happens on one page -- when the user clicks submit, the survey and its questions are created.
So far I have all the logic in the SurveysController but I'm not sure if this is MVC, especially because I have methods like add_question and remove_question.
Is there a preferred way of doing this?
If you're creating the questions through accepts_nested_attributes_for, then it would be okay.
--
When you mention add_question / remove question - this would be best handled in a separate questions controller (with nested resource routing):
#config/routes.rb
resources :surveys do
resources :questions, only: [:create, :destroy]
end
This allows you to use the following:
#app/controllers/surverys_controller.rb
class SurveysController < ApplicationController
def show
#survey = Survey.find params[:id]
#new_question = #survey.questions.new
end
end
#app/views/surveys/show.html.erb
<%= #survey.title %>
<% #survey.questions.each do |question| %>
<%= link_to "Remove", surveys_question_path(#survey, question), method: :delete %>
<% end %>
<%= form_for #question do |f| %>
<%= f.text_field :text %>
<%= f.submit %>
<% end %>
This would keep your controllers conventional.
Nested resources:
resources :surveys do
resources :questions # check http://localhost:3000/rails/info/routes for generated routes
end
Now you can create QuestionsController with normal CRUD actions and survey_id in params. I.e:
class QuestionsController
def create
#question = Question.new(survey_id: params[:survey_id], ... )
end
end
And to create survey with all questions at once use accepts_nested_attributes_for on Survey model http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Couldn't find User without an ID, rails 4

I have two user types: Artist and Fan. I want Fans to be able to follow Artists. So far following them does not work, but unfollowing does. I have create and destroy set up the same way, but can't seem to get it to work. I get the error Couldn't find Artist without an ID when trying to create a Relationship. Anyway I can find the Artist's ID?
Code below:
relationships_controller.rb
class RelationshipsController < ApplicationController
before_action :authenticate_fan!
def create
#relationship = Relationship.new
#relationship.fan_id = current_fan.id
#relationship.artist_id = Artist.find(params[:id]).id #the error
if #relationship.save
redirect_to (:back)
else
redirect_to root_url
end
end
def destroy
current_fan.unfollow(Artist.find(params[:id]))
redirect_to (:back)
end
end
artists_controller.rb
def show
#artist = Artist.find(params[:id])
end
artists/show.html.erb
<% if fan_signed_in? && current_fan.following?(#artist) %>
<%= button_to "unfollow", relationship_path, method: :delete, class: "submit-button" %>
<% elsif fan_signed_in? %>
<%= form_for(Relationship.new, url: relationships_path) do |f| %>
<%= f.submit "follow", class: "submit-button" %>
<% end %>
<% end %>
models/fan.rb
has_many :relationships, dependent: :destroy
has_many :artists, through: :relationships
belongs_to :artist
def following?(artist)
Relationship.exists? fan_id: id, artist_id: artist.id
end
def unfollow(artist)
Relationship.find_by(fan_id: id, artist_id: artist.id).destroy
end
models/artists.rb
has_many :relationships, dependent: :destroy
has_many :fans, through: :relationships
belongs_to :fan
routes.rb
resources :relationships, only: [:create, :destroy]
Basically, you need to send artist_id to the action. Change your form to this. There is a lot of refactoring required but this one will work for you:
<%= form_for(Relationship.new, url: relationships_path) do |f| %>
<%= hidden_field_tag :artist_id, #artist.id %>
<%= f.submit "follow", class: "submit-button" %>
<% end %>
In controller, you can access it like:
#relationship.artist_id = Artist.find(params[:artist_id]).id
I would consider solving this with a nested route instead:
resources :artists, shallow: true do
resources :relationships, only: [:create, :destroy]
end
this will create these routes in addition to the regular CRUD routes for artist:
Prefix Verb URI Pattern Controller#Action
artist_relationships POST /artists/:artist_id/relationships(.:format) relationships#create
relationship DELETE /relationships/:id(.:format) relationships#destroy
Notice that we use shallow: true which scopes the create route under /artists/:artist_id but not the destroy route.
You can then change your form:
<%= form_for(Relationship.new, url: artist_relationships_path(artist_id: #artist)) do |f| %>
<%= f.submit "follow", class: "submit-button" %>
<% end %>
And your controller:
class RelationshipsController < ApplicationController
before_action :authenticate_fan!
def create
current_fan.relationships.build(artist: Artist.find(params[:artist_id]))
if #relationship.save
redirect_to(:back)
else
redirect_to root_url # makes more sense to redirect back to #artist ?
end
end
def destroy
#relationship = current_fan.relationships.find(params[:id])
#relationship.destroy
redirect_to(:back)
# or redirect back to the artist page.
# redirect_to #relationship.artist
end
end
Notice how we also refactor the destroy action - You never want to have a route with an :id param which points to a completely different resource. Thats just poor REST design. We don't even need to know the artist ID if we know the id of a relationship. Instead here the ID refers to the Relationship resource.
To create a link to destroy the relationship you would do:
<%= link_to 'Unfollow', relationship_path(#relationship), method: :delete %>
And lets get rid of the Fan#unfollow method.
While we are at it we can fix the Fan#following? method.
def following?(artist)
relationships.exists?(artist: artist)
end
By using the relationship (in the ActiveRecord sense of the word!) instead of querying the Relationship model directly you can use eager loading to avoid additional queries and also you don't have to specify the fan.

Resources