Couldn't find User without an ID, rails 4 - ruby-on-rails

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.

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.

Setting up Controller for a reservations app

I am learning and building my first app with Ruby on Rails by cloning and adjusting an existing project. I got stuck in the writing a controller and hope that someone has a tip to help me out.
Context
I am building a trainings platform: Users can design a training, these trainings can be given on several dates (I call these trainingsessions 'thrills'), (other) users can subscribe to these thrills with reservations.
Conceptualization of models
Complication
I got the users, trainings and reservations model running now but I want to add the thrills model in between (I know this is not the easiest way, but I followed a training that did not include the thrills model). I setup the model and a simple view where a trainer should be able to add thrills to an existing training (in trainings/edit). Unfortunately after a day of trying I have not managed to do so, I keep getting:
NoMethodError in ThrillsController#create
NoMethodError (undefined method `thrills' for nil:NilClass): (line 16)
My TrainingsController for def edit looks like
def edit
if current_user.id == #training.user.id
#photos = #training.photos
#thrill = #training.thrills.create(thrill_params)
#thrills = #training.thrills
else
redirect_to root_path, notice: "Je hebt hier helaas geen toegang tot"
end
end
def update
if #training.update(training_params)
if params[:images]
params[:images].each do |image|
#training.photos.create(image: image)
end
end
#thrill = #training.thrills.create(thrill_params)
#thrills = #training.thrills
#photos = #training.photos
redirect_to edit_training_path(#training), notice: "Updated..."
else
render:edit
end
end
And my ThrillsController looks like
class ThrillsController < ApplicationController
def create
#thrill = #training.thrills.create(thrill_params)
redirect_to #thrill.training, notice: "Je thrill is aangemaakt!"
end
private
def thrill_params
params.require(:thrill).permit(:trilldate, :thrillhr, :thrillmin, :training_id)
end
end
And my form to add a thrill in views/thrills/_form.html.erb which is rendered in views/trainings/edit.html.erb
<%= form_for([#training.thrills.new]) do |f| %>
<div class="form-group">
<%= f.text_field :thrillhr, placeholder: "Uur", class: "form-control" %>
</div>
<%= f.hidden_field :training_id, value: #training.id %>
<div class="actions">
<%= f.submit "Create", class: "btn btn-primary" %>
</div>
<% end %>
The full code of the app can be found here https://github.com/chrisrutte/musc
Question
I'm obviously doing something simple wrong here, so I hope someone can provide me the hint how to save new thrills in my Thrills model.
If any more information is needed, don't hestitate to ask me.
Thanks in advance!
First lets look at what the models for this kind of setup would look like:
class Training < ActiveRecord::Base
has_many :thrills
has_many :users, through: :thrills
end
class Thrills < ActiveRecord::Base
belongs_to :training
has_many :reservations
has_many :users, through: :reservations
end
class Reservation < ActiveRecord::Base
belongs_to :user
belongs_to :thrill
has_one :training, though: :thrill
end
class User < ActiveRecord::Base
has_many :reservations
has_many :thrills, through: :reservations
has_many :trainings, through: :thrills
end
Make sure you read the Rails guide on associations as this is quite complex.
Notice that Reservation works as a join model.
Reservation also has an indirect relation to Training. This is to avoid having duplicate foreign keys on several levels. The same applies to the User model. The main reason is that ActiveRecord will only write the foreign key in once place!
When setting up the routes you will most likely want to use nesting:
resources :trainings, shallow: true do
resources :thrills
end
We can then setup the controller.
class ThrillsController < ApplicationController
before_action :set_training!, only: [:new, :create, :index]
before_action :set_thrill!, only: [:show, :edit, :update, :destroy]
# GET /trainings/:training_id/thrills/new
def new
#thrill = #training.thrills.new
end
# POST /trainings/:training_id/thrills
def create
#thrill = #training.thrills.new(thrill_params)
if #thrill.save
redirect_to #thrill
else
render :new
end
end
# GET /trainings/:training_id/thrills
def index
#thrills = #training.thrills
end
# this is not nested.
# GET /thrills/:id
def show
end
# this is not nested.
# GET /thrills/:id/edit
def edit
end
# this is not nested.
# PUT|PATCH /thrills/:id
def update
if #thrill.update(thrill_params)
redirect_to #thrill
else
render :edit
end
end
# ...
private
def set_training!
#training = Training.find(params[:training_id])
end
def set_thrill!
#thill = Thrill.joins(:training).find(params[:id])
#training = #thill.training
end
def thrill_params
params.require(:thrill)
.permit(:trilldate, :thrillhr, :thrillmin, :training_id)
end
end
And lets setup the form:
<%= form_for( [#training, #thrill] ) do |f| %>
<div class="form-group">
<%= f.text_field :thrillhr, placeholder: "Uur", class: "form-control" %>
</div>
<div class="actions">
<%= f.submit "Create", class: "btn btn-primary" %>
</div>
<% end %>
Since we want to pass the training_id though the form action attribute and not in the form body we use form_for( [#training, #thrill] ) which will give us the path /trainings/6/thrills for example.
However for our edit form we want /thrills/1 instead. So lets fix that:
<%= form_for( #thrill.new_record? ? [#training, #thrill] : #thrill ) do |f| %>
# ...
<% end %>

Rails: Associations and reference models

Background:
I have two models User(Devise model) and Client. I have setup associations such that Client belongs_to :user and User has_one :client. I have setup my database such that the clients table has a user_id attribute
Problem:
In my navbar, once a user is signed in, I want them to be able to access their "profile" which is represented by the Client model, however I cannot seem to access the proper client through the user_params. For example <%= link_to "My Profile", client_path(#user) %>
This results in the error No route matches {:action=>"show", :controller=>"clients", :id=>nil} missing required keys: [:id] even though the :id should be available since the user is signed in.
I have edited the Devise sessions_controller to contain:
def configure_sign_in_params
devise_parameter_sanitizer.permit(:sign_up) do |user_params|
user_params.permit( :email, :password, :id)
end
end
...but still have no luck. I have a feeling I'm missing something basic, I just need some help.
I'm missing something basic
You don't need #user, it needs to be current_user:
<%= link_to "Profile", client_path(current_user) if user_signed_in? %>
We use a similar pattern (User has_one Profile); we made a singular resource which negates the need to pass an object:
#app/models/user.rb
class User < ActiveRecord::Base
has_one :client
accepts_nested_attributes_for :client
end
#config/routes.rb
resource :profile, only: [:show, :update] #-> url.com/profile
#app/controllers/profile_controller.rb
class ProfileController < ApplicationController
def update
current_user.update user_params
end
private
def user_params
params.require(:user).permit(client_attributes: [:x, :y, :z])
end
end
#app/views/profile/show.html.erb
<%= form_for current_user do |f| %>
<%= f.fields_for :client do |c| %>
<%= c.text_field :x %>
<% end %>
<%= f.submit %>
<% end %>
This will allow you to send requests to... <%= link_to "Profile", profile_path %>.
Because you're only allowing the profile view for each user, you'll be able to call the current_user object for it.

Voting for nested objects using the Acts As Votable gem

I have a Restaurant model that has_many :dishes, through: dish_categories. I found a post that shows how to write the view code necessary to get things going for the Acts As Votable gem. My situation differs being that the dish model is the nested resource that's being voted upon.
I tried translating the provided code but to no avail. At this point should I create a new controller for dishes and place the votable actions there? If so how would I setup my route so I can accomplish this on my restaurant's show page?
Models
class Restaurant < ActiveRecord::Base
has_many :dish_categories, dependent: :destroy
has_many :dishes, through: :dish_categories
end
class DishCategory < ActiveRecord::Base
belongs_to :restaurant
has_many :dishes, dependent: :destroy
delegate :name, to: :dish_category, prefix: "category"
delegate :restaurant, to: :dish_category
end
class Dish < ActiveRecord::Base
belongs_to :dish_category
end
Restaurants Controller
...
def upvote
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
#dish.liked_by current_user
redirect_to #restaurant
end
def downvote
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
#dish.disliked_by current_user
redirect_to #restaurant
end
...
Routes
resources :restaurants do
member do
put "upvote", to: "restaurants#upvote"
put "downvote", to: "restaurants#downvote"
end
end
Restaurants - Show View
...
<% #restaurant.dishes.each do |dish| %>
<div>
<h2><%= dish.category_name %></h2>
<b><%= dish.name %></b>
<%= link_to "Upvote", like_restaurant_path(dish), method: :put %>
<%= link_to "Downvote", dislike_restaurant_path(dish), method: :put %>
</div>
<% end %>
A number of things needed to be done to get this to work. The first order of business was moving my controller action to my dishes controller. I also added two more actions: unlike and undislike for toggle functionailty.
NOTE: Logic for authenticating non-registered for users to liking/disliking dishes would still need to be written but this should help get you started.
Dishes Controller
class DishesController < ApplicationController
before_action :load_restaurant_and_dish, only: [:like, :unlike, :dislike, :undislike]
def like
#dish.liked_by current_user
redirect_to #restaurant
end
def unlike
#dish.unliked_by current_user
redirect_to #restaurant
end
def dislike
#dish.disliked_by current_user
redirect_to #restaurant
end
def undislike
#dish.undisliked_by current_user
redirect_to #restaurant
end
private
def load_restaurant_and_dish
#dish = Dish.find(params[:id])
#restaurant = #dish.restaurant
end
end
Next was configuring my routes to correspond with my restaurant and dish models:
Routes
resources :restaurants do
resources :dishes, only: [:like, :unlike, :dislike, :undislike] do
member do
put "like", to: "dishes#like"
put "unlike", to: "dishes#unlike"
put "dislike", to: "dishes#dislike"
put "undislike", to: "dishes#undislike"
end
end
end
I ended up refactoring my show view and created a few partials to reduce clutter now that there's a little bit of logic involved:
Restaurants - Show View
...
<%= render "restaurants/dish_partials/dishes" %>
...
Dishes Partial
<% #dishes.each do |dish| %>
<div>
<h2><%= dish.category_name %></h2>
<span><b><%= dish.name %></b></span>
<%= render "restaurants/dish_partials/like_toggle", dish: dish %>
</div>
<% end %>
Like Toggle Partial
<% if current_user.liked? dish %>
<%= link_to "Unlike", unlike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% else %>
<%= link_to "Like", like_restaurant_dish_path(#restaurant, dish), method: :put %>
<% end %>
<% if current_user.disliked? dish %>
<%= link_to "Undislike", undislike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% else %>
<%= link_to "Dislike", dislike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% end %>

How can I implement a 'create' action without 'new' action

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

Resources