I am new to Ruby on Rails.I am facing a problem using nested resources.
I am building a learning app where there are courses and lessons.
Every course will have many lessons and a lesson belongs to only one course.
I am unable to create a lesson for a course currently.
Example : http://localhost:3000/courses/19/lessons/new is a page where i want to create and display lessons for course 19.
Routes.rb
Rails.application.routes.draw do
devise_for :users
resources :courses
resources :courses do
resources :lessons
end
resources :lessons
root 'pages#landing'
get 'pages/home' => 'pages#home' ,as: :home
get '/user/:id' => 'pages#profile',as: :profile
get '/users' => 'courses#index',as: :user_root
end
Course.rb
class Course < ActiveRecord::Base
belongs_to :user
has_many :lesson
validates :user_id , presence: true
end
Lesson.rb
class Lesson < ActiveRecord::Base
belongs_to :course
validates :course_id , presence: true
end
CourseController.rb
class CoursesController < ApplicationController
def index
#courses = Course.all;
end
def new
#course = Course.new;
end
def create
#course = Course.new(course_params);
#course.user_id = current_user.id;
if #course.save
redirect_to course_path(#course)
else
flash[:notice]="Course could not be created ! "
redirect_to new_course_path
end
end
def edit
end
def update
end
def destroy
#course = Course.find(params[:id]);
#course.destroy;
end
def show
#course = Course.find(params[:id]);
end
private
def course_params
params.require(:course).permit(:title, :description, :user_id)
end
end
LessonController.rb
class LessonsController < ApplicationController
def index
#lessons = Lesson.all;
end
def new
#lesson = Lesson.new;
end
def create
#lesson = Lesson.new(lesson_params);
#course = Course.find_by(id: [params[:course_id]]);
if #lesson.save
redirect_to new_course_lesson_path , flash[:notice] = "Lesson successfully saved !"
else
redirect_to new_course_lesson_path , flash[:notice] = "Lesson cannot be created ! "
end
end
def show
#lesson = Lesson.find(params[:id])
end
private
def lesson_params
params.require(:lesson).permit(:title,:description,:video,:course_id)
end
end
Lessonform.html.erb
<%= form_for ([#course,#lesson]) do |f| %>
<%= f.label :lesson_Title %>
<%= f.text_field :title ,placeholder: "Enter the lesson Title" ,:class=>"form-control" %><br />
<%= f.label :Description %>
<%= f.text_area :description ,placeholder: "Enter the lesson Description",rows:"8",:class=>"form-control" %><br />
<center>
<%= f.submit "Create lesson",:class =>"btn btn-lg btn-primary" %>
</center>
<% end %>
One problem i see is that you have defined route resources :lessons twice. Once, inside courses scope and second time outside.
The error seems to occur because in your view #course is nil. So, please check you set #course in a before_action inside lessons_controller#new action.
EDIT
class LessonsController < ApplicationController
before_action :set_course, only: [:new, :create]
def new
#lesson = #course.lessons.build
end
private
def set_course
#course = Course.find_by(id: params[:course_id])
end
end
Also replace has_many :lesson with has_many :lessons inside Course model.
First change you need to make in your Course model as you have singular lesson when defining many association:
has_many :lessons
Also let me know if their are any chances of lessons page being called without courses? If no then please remove:
resources :lessons
I guess also the two defining of courses in routes in creating issue. Please try removing the:
resources :courses
Let me know if you still face any issue.
Related
I am new to rails and building my first app. I can't figure how to assign a car_id to new instances of awards that get created with the views/awards/new.html.erb file. Can anyone take a look and see where I am going wrong?
awards controller:
class AwardsController < ApplicationController
before_action :set_award, only: [:show]
def new
#award = Award.new
end
def index
#awards = Award.all
end
def create
#car = Car.params
#award = Award.new(award_params)
#award.save
redirect_to user_path(current_user)
end
def show
end
def destroy
end
private
def set_award
#award = Award.find(params[:id])
end
def award_params
params.require(:award).permit(:title, :year, :description)
end
end
cars controller
class CarsController < ApplicationController
before_action :set_car, only: [:show, :edit, :update, :destroy]
def new
#car = Car.new
end
def index
#cars = Car.all
end
def create
#car = Car.new(car_params)
#car.save
current_user.cars << #car
redirect_to current_user, :flash => { :success => "car created!" }
end
def edit
end
def update
#car.update(car_params)
if #car.valid?
#car.save
redirect_to user_path(current_user)
else
render :edit
end
end
def show
#car = Car.find(params[:id])
end
def destroy
#car.destroy
redirect_to user_path(current_user)
end
private
def set_car
#car = Car.find(params[:id])
end
def car_params
params.require(:car).permit(:make,:model,:year,:color)
end
end
car.rb
class Car < ApplicationRecord
belongs_to :user
has_many :awards
end
award.rb
class Award < ApplicationRecord
belongs_to :car
has_one :user, through: :cars
end
routes
Rails.application.routes.draw do
resources :awards
resources :cars
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
resources :users
root 'welcome#home'
resources :users do
resources :cars
end
resources :cars do
resources :awards
end
end
#awards form
<%= form_for #award do |f| %>
<%= f.label :title %>
<%= f.text_field :title %><br>
<%= f.label :year %>
<%= f.text_field :year %><br>
<%= f.label :description %>
<%= f.text_area :description %><br>
<%= f.submit %>
<%end%>
I need to be able to assign awards to specific cars, does anyone know how I can go about doing this?
Since you are using nested routes:
resources :cars do
resources :awards
end
Then:
app/controllers/awards_controller.rb
class AwardsController < ApplicationController
# add more into the array as you need
before_action :set_car, only: [:new, :create]
def new
#award = #car.awards.new
end
def create
#award = #car.awards.new(award_params)
#award.save
redirect_to user_path(current_user)
end
private
def set_car
#car = Car.find(params[:car_id])
end
end
app/views/awards/new.html.erb
<%= form_for [#car, #award] do |f| %>
...
Trivia:
because you are using nested routes, then your links would be something like:
<%= link_to 'New Award', new_car_award_path(SOMECAR) %>
<%= link_to 'Awards', car_awards_path(SOMECAR) %>
<%= link_to 'Edit Award', edit_car_award_path(SOMECAR, SOMEAWARD) %>
<%= link_to 'Delete Award', car_award_path(SOMECAR, SOMEAWARD), method: :delete %>
...just replace SOMECAR and SOMEAWARD with a Car and Award instances respectively, that you want to be in that link.
Recommendations:
handle validations errors. See tutorial
If possible, use shallow nesting
I have a model pitch where i am fetching grounddetail_id. I want to show all the pitch available in the ground. How i can book pitch of ground..
grounddetails_controller.rb
class GrounddetailsController < ApplicationController
before_action :find_ground, only: [:show, :edit, :destroy, :update]
def index
#grounddetails = Grounddetail.all.order('created_at DESC')
end
def new
#grounddetail = Grounddetail.new
end
def edit
end
def show
end
def create
#grounddetail = Grounddetail.new(ground_params)
if #grounddetail.save
redirect_to #grounddetail
else
render 'new'
end
end
def update
if #grounddetail.update(ground_params)
redirect_to #grounddetail
else
render 'edit'
end
end
def destroy
#grounddetail.destroy
redirect_to root_path
end
private
def find_ground
#grounddetail = Grounddetail.find(params[:id])
end
def ground_params
params.require(:grounddetail).permit(:name, :working_hours, :end_time, :address, :contact_no, :email, :number_of_grounds, :description, :featured_ground)
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users
devise_for :admins
resources :grounddetails do
resources :pitches
end
root "grounddetails#index"
end
model
grounddetail.rb
class Grounddetail < ActiveRecord::Base
has_many :pitches, dependent: :destroy
end
pitch.rb
class Pitch < ActiveRecord::Base
belongs_to :grounddetail
end
for now i just have pitch model and routes but in controller i am confused what to use. i can i book pitch of the ground. But for single ground i am able to book.
In the grounddetails#show action
def show
#pitches = #grounddetail.pitches
end
Then in the groundetails/show.html.erb, you can just use <%= #pitches %> to display the pitches of that grounddetail.
Update:
#in the show.html.erb
<% #pitches.each do |p| %>
<%= form_tag book_pitch_path(p) do %>
#your attributes here
<%= submit_tag "Book this Pitch" %>
<% end %>
<% end %>
#routes.rb
post :book_pitch/:id, to: 'pitches/book_pitch', as: 'book_pitch'
#in pitches_controller.rb
def book_pitch
#your actions here
end
I have three models: User, Publisher and Interest all with many to many relationships linked through three join models but only 2 out of 3 join models record the id's of their 2 parent models. my UsersPublisher model does not link User to Publisher.
My Interestscontroller proccesses a form (see code) through which I ask the user to provide Interest and Publisher. The latter gets processed via the fields_for method which allows you to pass Publisher attributes via the InterestsController. the UsersPublisher join model records the user_id but the publisher_id is nil.
I've tried putting #users_publishers in both the new and create methods of Publishers- and InterestsController. My latest attempt of using after_action in the InterestsController (see code) has also failed. I've also tried the after_action way in the PublishersController
Your helped is highly appreciated!
The UsersPublisher join model
class UsersPublisher < ActiveRecord::Base
belongs_to :user
belongs_to :publisher
end
InterestsController
class InterestsController < ApplicationController
before_action :find_user
after_action :upublisher, only: [:new]
def index
#interests = policy_scope(Interest)
end
def show
#interest = Interest.find(params[:id])
end
def new
#interest = Interest.new
#interest.publishers.build
authorize #interest
end
def create
#interest = Interest.new(interest_params)
#users_interests = UsersInterest.create(user: current_user, interest: #interest)
authorize #interest
if #interest.save
respond_to do |format|
format.js
format.html {redirect_to root_path}
end
flash[:notice] = 'Thank you, we will be in touch soon'
else
respond_to do |format|
format.js { render }
format.html { render :new }
end
end
end
def edit
#interest = Interest.find(params[:id])
authorize #interest
end
def update
#interest = Interest.find(params[:id])
#interest.update(interest_params)
if #interest.save
flash[:notice] = 'Your interest has been added'
else
flash[:notice] = 'Oops something went wrong'
end
end
private
def interest_params
params.require(:interest).permit(:name, publishers_attributes: [:publisher,:id, :feed])
end
def find_user
#user = current_user
end
def upublisher
#users_publishers = UsersPublisher.create(publisher: #publisher, user: current_user)
end
end
Form
<%= form_for [#user, #interest] do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :publishers do |ff| %>
<%= ff.label :publisher %>
<%= ff.text_field :publisher %>
<%= ff.label :feed %>
<%= ff.text_field :feed %>
<%end%>
<%= f.submit "Submit" %>
<%end%>
Since you're using fields_for, you'll want to make sure you have accepts_nested_attributes_for:
class UsersPublisher < ActiveRecord::Base
belongs_to :user
belongs_to :publisher
accepts_nested_attributes_for :publisher
end
This should fix your issue (if it's as you outlined).
Your question is pretty broad, so I don't know whether the above will work. Below are my notes...
From the looks of it, your structure is very complicated; you should work to make it as simple as possible. In the case of creating "interests", you may wish to get rid of the form completely:
#config/routes.rb
resources :publishers do
resources :interests, path: "interest", only: [:create, :destroy] #-> url.com/publishers/:publisher_id/interest
end
#app/controllers/interests_controller.rb
class InterestsController < ApplicationController
before_action :set_publisher
def create
current_user.interests.create publisher: #publisher
end
def destroy
#interest = current_user.interests.find_by publisher_id: #publisher.id
current_user.interests.delete #interest
end
private
def set_publisher
#publisher = UserPublisher.find params[:publisher_id]
end
end
You'd be able to use the above as follows:
<%= link_to "Add Interest", publisher_interest_path(#publisher), method: :post %>
<%= link_to "Remove Interest", publisher_interest_path(#publisher), method: :delete %>
Thinking about it properly, you've got a pretty bad structure.
I'd do something like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :interests
has_many :publishers, through: :interests
end
#app/models/interest.rb
class Interest < ActiveRecord::Base
belongs_to :user
belongs_to :publisher
accepts_nested_attributes_for :publisher
end
#app/models/publisher.rb
class Publisher < ActiveRecord::Base
has_many :interests,
has_many :users, through: :interests
end
This should give you the ability to create interests for any number of users and publishers. If you create a publisher for a specific user, you can use accepts_nested_attributes_for to pass the appropriate data:
#config/routes.rb
resources :users do
resources :interest, only: [:new, :create, :destroy] #-> url.com/users/:user_id/interests/new
end
#app/controllers/interests_controller.rb
class InterestsController < ApplicationController
def new
#user = User.find params[:user_id]
#interest = #user.interests.new
#interest.publisher.build
end
def create
#user = User.find params[:user_id]
#interest = #user.interests.new interest_params
end
private
def interest_params
params.require(:interest).permit(:user, :publisher)
end
end
#app/views/interests/new.html.erb
<%= form_for [#user, #interest] do |f| %>
<%= f.fields_for :publisher do |p| %>
<%= p.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>
I am trying to set up a 5 star rating system so users can rate other users. At the moment everything is working, (create, delete, update etc...) but only the logged in user can rate himself. I cannot rate other users. I get no errors, it just redirects to the user profile page as it should but without added a rating to that user.
user.rb
class User < ActiveRecord::Base
has_many :reviews
review.rb
class Review < ActiveRecord::Base
belongs_to :user
end
reviews_controller.rb
class ReviewsController < ApplicationController
before_action :find_user
before_action :find_review, only: [:edit, :update, :destroy]
def new
#review = Review.new
end
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
if #review.save
redirect_to user_path(#user)
else
render 'new'
end
end
def edit
end
def update
if #review.update(review_params)
redirect_to user_path(#user)
else
render 'edit'
end
end
def destroy
#review.destroy
redirect_to user_path(#user)
end
private
def review_params
params.require(:review).permit(:rating, :comment)
end
def find_user
#user = User.find(params[:user_id])
end
def find_review
#review = Review.find(params[:id])
end
end
_form which then gets rendered on show page:
<%= simple_form_for([#user, #user.reviews.build]) do |f| %>
<div id="rating-form">
<label>Rating</label>
</div>
<%= f.input :comment %>
<%= f.button :submit %>
<% end %>
<script>
$('#rating-form').raty({
path: '/assets/',
scoreName: 'review[rating]'
});
</script>
Any help getting this to work would be greatly appreciated!!
Do this:
#config/routes.rb
resources :users do
resources :reviews, only: [:new, :create]
end
#app/models/review.rb
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :reviewed, class_name: "User", foreign_key: :reviewed_id
end
#app/controllers/reviews_controller.rb
class ReviewsController < ApplicationController
def new
#review = current_user.reviews.new
end
def create
#review = current_user.reviews.new review_params
#review.save
end
private
def review_params
params.require(:review).permit(:rating, :comment).merge(reviewed_id: params[:user_id])
end
end
#app/views/reviews/new.html.erb
<%= form_for #review do |f| %>
<%= f.number_field :rating %>
<%= f.text_field :comment %>
<%= f.submit %>
<% end %>
This would mean you'll have to include a reviewed_id column in your reviews table.
You'll be able to access it using: url.com/users/:user_id/reviews/new
The application will automatically fill the user_id and reviewed_id fields, so the rest of your code should work with the upgrade.
The big problem you have is that you're basically recording the user_id (presumably of who created the review)... but have no way of stipulating who the review is about.
The above code fixes that for you.
I'm trying to do user reviews, when user can write review to another user, i create tables review with :content, user_reviews with :for_user_id, and by_user_id,
my routes
devise_for :users
resources :users, :only => [:show] do
resources :reviews
end
class Review < ActiveRecord::Base
belongs_to :user
has_many :users, :through => :users_reviews
end
class User < ActiveRecord::Base
has_many :users_review
has_many :reviews, :through => :users_review
end
class UsersReview < ActiveRecord::Base
belongs_to :user
belongs_to :review
end
class ReviewsController < ApplicationController
def new
#user = User.find(params[:user_id])
#review = #user.reviews.new(params[:for_user_id])
end
def create
#user = User.find(params[:id])
#review = current_user.reviews.build(review_params)
redirect_to root_path
end
def show
end
def index
#user = User.find(params[:for_user_id])
#reviews = Review.all
end
private
def review_params
params.require(:review).permit(:user_id, :user_id, :content)
end
end
and my view
<%= form_for([#user, #user.reviews.build]) do |f| %>
<%= f.text_area :content, placeholder: "Your review" %>
<%= f.submit "Go", class: "btn btn-large btn-primary" %>
<% end %>
all work, but no data send to the db :\ what i doing wrong?
You're not saving anything in the create method, therefore nothing is going to persist.
You'll want something like:
def create
#user = User.find(params[:id])
#review = #user.reviews.build(review_params)
if #user.save && #review.save
redirect_to root_path
else
(handle bad data)
end
end
I would also tend to agree with #marzapower - If you want to use current_user, you don't need the line above #review. My method above has this change included.
You are missing a call to the save method in your create action:
def create
#user = User.find(params[:id])
#review = current_user.reviews.build(review_params)
#review.save
redirect_to root_path
end
This post explains the difference between build and create.
I think you're missing two things. The User and Review classes both need to reference the UserReview class that they employ in the through relationship, e.g.,
class User < ActiveRecord::Base
...
has_many :user_reviews_received, class_name: 'UserReview', foreign_key: :for_user_id
has_many :reviews_received, through: :user_reviews_received, class_name: 'Review'
has_many :user_reviews_written, class_name: 'UserReview', foreign_key: :by_user_id
has_many :reviews_written, through: :user_reviews_written, class_name: 'Review'
...
end
As noted above, the create action seems a bit confused. Generally speaking the params should be the same for building up an instance in the new and create actions but you've got some differences. You could resolve this in one of two ways. One option would be to resolve it in the controller.
class ReviewsController < ActionController::Base
def new
#user = User.find(params[:user_id])
#review = #user.reviews_received.new(by_user_id: current_user.id)
end
def create
#review = #user.reviews_received.create(params[:review].merge(by_user_id: current_user.id))
redirect_to root_path
end
end
The second option would be to resolve it in the view (new first line in the form).
<%= form_for([#user, #user.reviews_received.build(by_user_id: current_user.id)]) do |f| %>
<%= f.hidden_field :by_user_id %>
<%= f.text_area :content, placeholder: "Your review" %>
<%= f.submit "Go", class: "btn btn-large btn-primary" %>
<% end %>
I would tend towards something like the first option
There's an error in the controller, I think:
def create
#user = User.find(params[:id])
#review = #user.reviews.build(review_params)
#review.save
redirect_to root_path
end
You were creating new Reviews for current_user instead of #user.