Nested resources, undefined method nilClass - ruby-on-rails

I am getting an error "undefined method " when I try to build a nested resource on action 'new' in rails 4.2
Here's my routes:
devise_for :medics
resources :patients, shallow: true do
resources :consultations do
resources :prescriptions
end
end
I have Devise for system logon, and rather than use "User" to the model name, I use "Medic" in order to use the registry to devise to create a type of medical profile with a new fields like name, phone, etc. (I don't know if here's the problem)...
Patients controller:
class PatientsController < ApplicationController
before_action :set_medic
def new
#patient = #medic.patients.new
end
def create
#patient = #medic.patients.new(patient_params)
end
def set_medic
#medic = Medic.find_by_id(params[:medic_id])
end
Model:
class Medic < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :patients, :dependent => :destroy
end
class Patient < ActiveRecord::Base
belongs_to :medic, :foreign_key => :medic_id, :dependent => :destroy
has_many :consultations
accepts_nested_attributes_for :consultations
end
View:
<%= link_to 'New Patient', new_patient_path(#medic) %>
rake routes:
new_patient GET /patients/new(.:format) patients#new
Error:
undefined method `patients' for nil:NilClass
in this line: #patient = #medic.patients.new
Any idea? thaks in advance

The problem is very simple.
You're calling the following on each request:
def set_medic
#medic = Medic.find_by_id(params[:medic_id])
end
The problem is that you're not passing medic_id through your routes:
devise_for :medics
resources :patients, shallow: true do #-> no medic_id here
resources :consultations do #-> or here
resources :prescriptions #-> or here
end
end
Therefore, what happens is that you're trying to find a Medic without any id, hence the NilClass error.
You're getting confused with the nested resources directive in Rails routes:
#DON'T use this - it's just an example
#config/routes.rb
resources :medics do
resources :patients #-> url.com/medics/:medic_id/patients/:id
end
As you're using Devise, I think you'd be able to get away with scoping your calls around the current_medic helper (which is what I presume you're doing)...
-
Fix
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def new
#patient = current_medic.patients.new
end
def create
#patient = current_medic.patients.new(patient_params)
end
end
This way, you'll be able to use (as you're using current_medic):
<%= link_to "New", new_patient_path %>

Since you didn't put the medic_id in your route, you probably have to clarify the parameter in your view like this:
<%= link_to 'New Patient', new_patient_path(:medic_id => #medic.id) %>

Related

How do you toggle the state from a user with buttons on a page from another table in rails?

I have a rails application with Events and Users and compared them in a third table UserEventStates, which belongs to Users and Events and has an integer named state and makes it possible to set the state for an user for any event separately/different. Now I would like to update the state for the user for this event when the matching button was clicked, how can I do this with Turbo?
# models/event.rb
class Event < ApplicationRecord
has_many :user_event_states
has_many :users, through: :user_event_states
accepts_nested_attributes_for :user_event_states
end
# models/user.rb
class User < ApplicationRecord
has_many :user_event_states
has_many :events, through: :user_event_states
accepts_nested_attributes_for :user_event_states
end
# models/user_event_state.rb
class UserEventState < ApplicationRecord
belongs_to :event
belongs_to :user
end
Updated answer:
If I got the relationships right:
# models/event.rb
class Event < ApplicationRecord
has_many :user_event_states
has_many :users, through: :user_event_states
end
# models/user.rb
class User < ApplicationRecord
has_many :user_event_states
has_many :events, through: :user_event_states
accepts_nested_attributes_for :user_event_states
end
# models/user_event_state.rb
class UserEventState < ApplicationRecord
belongs_to :event
belongs_to :user
accepts_nested_attributes_for :user_event_states
end
And I understand your goal:
I would like to have buttons for each user and update only the state from the selected user
Then here are some things I would change:
Create a UserEventStatesController:
# app/controllers/user_event_states_controller.rb
class UserEventStatesController < ApplicationController
before_action :set_event
before_action :set_user_event_state, only: %i[show edit update destroy]
before_action :set_user, only: %i[show edit update destroy]
...
def update
if #user_event_state.update(user_event_state_params)
# action to perform on success
else
# action to perform on fail
end
end
...
private
def set_event
#event = Event.find(params[:event_id])
end
def set_user_event_state
#user_event_state = #event.user_event_states.find(params[:id])
end
def set_user
#user = #user_event_state.user
end
def user_event_state_params
params.require(:user_event_state).permit(:event_id, :user_id, :state)
end
end
And change your EventsController back to a more standard controller:
#app/controllers/events_controller.rb
before_action :set_event, only: %i[ show edit update destroy]
def show
# get *ALL* UserEventStates for this Event
#ues_events = UserEventState.where(event_id: #event.id)
end
private
def set_event
#event = Event.find(params[:id])
end
def event_params
params.require(:event).permit(:description, :date, :meeting_time, :start_time, :end_time)
end
Nest UserEventState routes under Event:
# config/routes
devise_for :users
resources :events do
resources :user_event_states
end
Run rails routes from your terminal to check the prefixes of the routes. Should look like this:
$ rails routes
Prefix Verb URI Pattern Controller#Action
...
event_user_event_states GET /events/:event_id/user_event_states user_events_states#index
POST /events/:event_id/user_event_states user_events_states#create
new_event_user_event_state GET /events/:event_id/user_event_states/new user_events_states#new
edit_event_user_event_state GET /events/:event_id/user_event_states/edit user_events_states#edit
event_user_event_state GET /events/:event_id/user_event_states/show user_events_states#show
PATCH /events/:event_id/user_event_states/:id user_events_states#update
PUT /events/:event_id/user_event_states/:id user_events_states#update
DELETE /events/:event_id/user_event_states/:id user_events_states#destroy
Now to views:
#app/views/events/show.html.erb
<% #ues_events.each do |ues_ev| %>
<%= ues_ev.user.email %>
<%= ues_ev.state %>
<%= ues_ev.night %>
<% form_with model: ues_ev, url: [#event, ues_ev] do |form| %>
<%= form.text_field :state %> <!-- or whatever kind of input :state needs to be -->
<%= form.submit 'Zusagen' %>
<% end %>
The form should submit a PATCH request to /events/:event_id/user_event_states/:id, which will hit the UserEventStatesController#update action.
The params[:user_event_state] should be { state: 'form response' }
upgrade idea:
Submitting a form and getting redirected can be slow and not very smooth in terms of user experience.
Say you want to say right on the events#index page and just click buttons to toggle the state for a few users.
You could look at replacing your forms with buttons or links that use:
Rails 6 and prior: unobtrusive javascript & AJAX to submit data remotely. Here's a tutorial I recommend
Rails 7: use Turbo
First answer:
get 'right route' can be whatever you want, as long as it is a viable URI path.
Any symbols in the path are accessible via params
So get 'events/:events_id/users/:id/update-event-state' to: ...
Would have params[:event_id] and params[:id].
Since it looks like you need only :event_id params, it doesn't have to include user.
get 'events/:event_id/user-state', to: ...
But it also doesn't have to follow convention:
get 'foo/bar/baz/:event_id', to: ...
If you want to nest the route, you can, but you lose control over what the URL will be:
resources :events do
# url would be events/:id/update_user_event_state
get :update_user_event_state, on: :member
resources :users do
# url would be events/:event_id/users/update_user_event_state
get :update_user_event_state, on: :collection
# url would be events/:event_id/users/:user_id/update_user_event_state
get :update_user_event_state, on: :member
end
end
Also, I think you have a typo. It should be like this, I think:
def update_user_event_state
#ues_event.update(state: 1)
# not self.update
end

Relationship between models are right but Controller can't find any records?

I have a Payment, Client and PaymentTransactions model. I'm trying to show all PaymentTransactions for a given Client but the controller returns an empty array.
The route I'm using to get there is /api/v1/clients/:id/payment_transactions
This is my Routes file:
Rails.application.routes.draw do
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :payments, only: [ :index ]
resources :clients, only: [ :index ]
resources :clients, only: [:show] do
resources :payment_transactions, only: [:index]
end
end
end
end
Rails routes:
Uri Pattern /api/v1/clients/:client_id/payment_transactions
Controller#Action api/v1/payment_transactions#index {:format=>:json}
These are my models:
class Payment < ApplicationRecord
belongs_to :client, optional: true, dependent: :destroy
has_many :payment_transactions, dependent: :destroy
end
class Client < ApplicationRecord
has_one :payment, dependent: :destroy
has_many :payment_transactions, through: :payment, dependent: :destroy
end
class PaymentTransaction < ApplicationRecord
belongs_to :payment, optional: true
has_one :client, through: :payment
end
And this is my controller:
class Api::V1::PaymentTransactionsController < Api::V1::BaseController
before_action :set_client
def index
#transactions = PaymentTransaction.where(client: #client)
end
private
def set_client
#client = Client.find(params[:client_id])
end
end
I can do PaymentTransaction.client and get the Client associated to that PaymentTransaction. I can do Client.payment_transactions to get all transactions for the given Client as well.
However, making a call to /api/v1/clients/1/payment_transactions returns [].
EDIT:
A change in the controller action to #transactions = PaymentTransaction.where(client_id: #client.id) resulted in
ActionView::Template::Error (PG::UndefinedColumn: ERROR: column payment_transactions.client_id does not exist
LINE 1: ...transactions".* FROM "payment_transactions" WHERE "payment_t...
^
):
1: json.array! #transactions do |transaction|
2: json.extract! transaction, :id, :payment_id, :transaction_identification, :amount, :status
3: end
ActiveRecord's where method expects its input to be columns on the called model, so you can't use it to filter by intermediate associations. Instead, you can call the defined payment_transaction asosciation directly from the #client object.
def index
#transactions = #client.payment_transactions
end
Side note:
Using the association directly has the added advantage of allowing you to eager load the records later on, in case you ever need to list clients alongside their payment transactions. e.g.
Client.all.includes(:payment_transactions)
Using a where won't make this easily doable.

Couldn't find Course without an ID

When I try to access app/views/companies/courses/show.html.erb(which is redirected after app/views/companies/courses/new.html.erb), rails server says that it cannot find a course without an ID.
Also, when I run Course.all on rails console, the courses have the correct id, the correct description and the correct company id but no name attribute.
App/controllers/companies/courses_controller:
class Companies::CoursesController < ApplicationController
before_action :authenticate_company!
def new
#course = Course.new
end
def create
#course = current_company.courses.create(course_params)
if #course.save
redirect_to companycourse_path(:course => #course.id)
else
render 'new'
end
end
def show
#course = current_company.courses.find(params[:id])
end
def index
#courses = current_company.courses.all
end
private
def course_params
params.require(:course).permit(:title, :description)
end
end
App/models/course.rb:
class Course < ApplicationRecord
belongs_to :company
end
App/models/company.rb:
class Company < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :courses
end
Config/routes.rb:
Rails.application.routes.draw do
devise_for :companies
devise_for :users
root 'static_pages#home'
get 'users/:id' => 'users#show'
resources :users, only: [:show]
get 'companies/:id' => 'companies#show'
resources :companies, only: [:show] do
resources :courses, only: [:show,:new,:create,:index]
end
devise_scope :user do
get 'signup', to: 'users/registrations#new'
get 'login', to: 'users/sessions#new'
end
devise_scope :company do
get 'companysignup', to: 'companies/registrations#new'
get 'companylogin', to: 'companies/sessions#new'
get 'newcourse', to:'companies/courses#new'
post 'newcourse', to:'companies/courses#create'
get 'companycourse', to:'companies/courses#show'
get 'companycoursesindex', to:'companies/courses#index'
end
end
I had a similar problem building my app not long ago and my problem was coming from controllers.
In your routes.rb I would add resources :companies, :has_many => :courses.I also would add a validation to your course model in models/course.rb to make sure that when you save it to the database your object has a title: class Course < ApplicationRecord
belongs_to :company validates :title, presence: :true
end
Also try using this or something along lines in your courses_controller.rb instead of your current code:
def create
#company = find_company
#course = Course.new(course_params)
#course.company_id = current_company.id
if #course.save
your conditions here
else
your conditiona here
end
end
private
def find_company
params.each do |name, value|
if name == 'company_id'
return #company = Company.find(value.to_i)
end
end
nil
end
I hope it helps.

Destroy a has_many :through is not working with link_to helper

I'm working on a simple project management tool in Rails 4, and the part which gives me headaches has three main models: Projects, Users and Memberships.
Users can have many projects and projects can have many users. I implemented a has_many through membership relationship between Projects and Users in the following manner:
Project:
class Project < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
User:
class User < ActiveRecord::Base
has_many :memberships
has_many :projects, through: :memberships
end
Membership:
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
So far I haven’t seen it on Stack Overflow, but I created a seperate controller for the Memberships, with a :create and :destroy only.
So far, :create seems to work just fine.
The main problem lies in the destroy function of the Memberships.
The destroy function I implemented is:
def destroy
Membership.find(:id).destroy
redirect_to current_project || request.referer
end
rake routes says that the membership path exists, but the following tries give me:
I tried to use a link_to helper to delete the memberships:
<%= link_to "delete", membership, method: :delete %>
EDIT: error: undefined local variable or method `membership'
<%= link_to "delete", #membership, method: :delete %>
EDIT: error: Sorry something went wrong --> goes to /memberships
<%= link_to "delete, membership_path(#membership), method: :delete %>
EDIT: error No route matches {:action=>"destroy", :controller=>"memberships", :id=>nil} missing required keys: [:id]
which all give errors.
EDIT: on request also the projects_controller #show function
def show
#user = current_user
#project = current_user.projects.find(params[:id])
#members = #project.users
#projects = #user.projects
#membership = #project.memberships.build if logged_in?
#memberships = #project.memberships
end
How can I make sure a membership is removed with the associated id in #project.membership_ids? Should I include certain extra parameters?
resources :memberships, only: [:create, :destroy]
Update
Don't know how I missed this earlier, in your destroy action you have Membership.find(:id).destroy. It should utilize params and be more along the lines of this:
# MembershipsController
def destroy
#membership = Membership.find(params[:id])
if #membership.destroy
redirect_to current_project || request.referer
else
#
end
end
Your ProjectsController's Show action isn't defining #membership as a Membership object.
def show
#user = current_user
#project = #user.projects.find(params[:id])
#membership = Membership.find_by user: #user, project: #project
# build is used for nested attributes, not sure why you'd have this in a show action...
# #project.memberships.build if logged_in?
# the following are redundant.
#projects = #user.projects
#members = #project.users
#memberships = #project.memberships
end
In the view: <%= link_to "delete", #membership, method: :delete %>
If you want all memberships of a project or user to be destroyed upon parent deletion, make the following changes to your User & Project models:
class Project < ActiveRecord::Base
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
end
class User < ActiveRecord::Base
has_many :memberships, dependent: :destroy
has_many :projects, through: :memberships
end
The reason the your code did not work is because you did not specify the path to link_to. I do not believe you need to have #membership so I edited that out.
<%= link_to "delete", membership_path(#membership), method: :delete %>

has_many :through broke some code

So i'm relatively new to RoR, and am having some issues in trying to get my code back up and working. So previously I had users, and wikis that users could create. I've set up so that users can subscribe and get premium status to make wikis private. Now I'm in the process of making it so that Premium users can add standard users as collaborators to the wiki. I've decided to got about associating them through has_many :through relationships.
The issue I'm running into so that some of my buttons have started making errors that I don't understand. The one I'm stuck on right now is when showing the page that has a create new wiki button on it.
This is the error I am getting when I added the has_many through: relationship
No route matches {:action=>"new", :controller=>"wikis", :format=>nil, :user_id=>nil} missing required keys: [:user_id]
Here are the models:
collaborator.rb
class Collaborator < ActiveRecord::Base
belongs_to :wiki
belongs_to :user
end
user.rb
class User < ActiveRecord::Base
...
has_many :collaborators
has_many :wikis, :through => :collaborators
end
wiki.rb
class Wiki < ActiveRecord::Base
belongs_to :user
has_many :collaborators
has_many :users, :through => :collaborators
end
The important bits of the wiki_controller.rb
def new
#user = User.find(params[:user_id])
#wiki = Wiki.new
authorize #wiki
end
def create
#user = current_user
#wiki = #user.wikis.create(wiki_params)
authorize #wiki
if #wiki.save
flash[:notice] = "Wiki was saved"
redirect_to #wiki
else
flash[:error] = "There was an error saving the Wiki. Please try again"
render :new
end
end
And finally the show.html.erb file the button is located in.
<div class="center-align">
<%= link_to "New Wiki", new_user_wiki_path(#user, #wiki), class: 'btn grey darken-1' %>
</div>
If I'm missing any files or relevant info please let me know. This may be a simple stupid answer but I'm stuck for the life of me.
Thanks in advance.
Edit:
Here is the requested added info, first up the show info in the users_controllers.rb
def show
#wikis = policy_scope(Wiki)
end
the corresponding policy scope I'm using in the user_policy.rb
class UserPolicy < ApplicationPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
wikis = []
all_wikis = scope.all
all_wikis.each do |wiki|
if wiki.user == user || wiki.users.include?(user)
wikis << wiki
end
end
end
wikis
end
end
and the route.rb file
Rails.application.routes.draw do
devise_for :users
resources :users, only: [:update, :show] do
resources :wikis, shallow: true
end
resources :wikis, only: [:index]
resources :charges, only: [:new, :create]
delete '/downgrade', to: 'charges#downgrade'
authenticated do
root to: "users#show", as: :authenticated
end
root to: 'welcome#index'
end
Hope it helps
I found out the problem. I set up the migrate file wrong when originally creating the collaboration model.
Thanks for all of your help.

Resources