I have the following relationship between Classroom and Student models :
Classroom has_many :students
Student belongs_to :classroom
In my Classroom model I have these relation callbacks :
has_many :students,
after_add: :update_student_count,
before_remove: :update_student_count
def update_student_count(student)
self.student__count = students.count
self.save
end
In my Student controller I have :
def destroy
student = Student.find params[:id]
student.destroy!
redirect_to action: :index
end
However student.destroy! never triggers the before_remove callback in my Classroom model.
I have tried writing the action destroy in the following way to be executing the destroy action on the classroom instance but it seems destroy cant be used in this way with mongoid...
def destroy
student = Student.find params[:id]
classroom= student.classroom
student.destroy!
classroom.students.destroy(student)
redirect_to action: :index
end
Why is my before_remove callback never executed ?
Try before_destroy instead.
Here's the Mongoid docs for callbacks
Just to flush out teddybear's solution, I think you could do something like this:
class Student
belongs_to :classroom
after_destroy :update_student_count
after_create :update_student_count
def update_student_count
classroom.update_student_count
end
end
class Classroom
has_many :students
def update_student_count
self.student__count = students.count
save
end
end
Related
I have the following classes:
class Product < ApplicationRecord
belongs_to :product_category
def destroy
puts "Product Destroy!"
end
end
class ProductCategory < ApplicationRecord
has_many :products, dependent: :destroy
def destroy
puts "Category Destroy!"
end
end
Here, I am trying to override the destroy method where I eventually want to do this:
update_attribute(:deleted_at, Time.now)
When I run the following statement in Rails console: ProductCategory.destroy_all I get the following output
Category Destroy!
Category Destroy!
Category Destroy!
Note: I have three categories and each category has more than one Products. I can confirm it by ProductCategory.find(1).products, which returns an array of products. I have heard the implementation is changed in Rails 5. Any points on how I can get this to work?
EDIT
What I eventually want is, to soft delete a category and all associated products in one go. Is this possible? Or will ave to iterate on every Product object in a before destroy callback? (Last option for me)
You should call super from your destroy method:
def destroy
super
puts "Category destroy"
end
But I definitely wouldn't suggest that you overide active model methods.
So this is how I did it in the end:
class Product < ApplicationRecord
belongs_to :product_category
def destroy
run_callbacks :destroy do
update_attribute(:deleted_at, Time.now)
# return true to escape exception being raised for rollback
true
end
end
end
class ProductCategory < ApplicationRecord
has_many :products, dependent: :destroy
def destroy
# run all callback around the destory method
run_callbacks :destroy do
update_attribute(:deleted_at, Time.now)
# return true to escape exception being raised for rollback
true
end
end
end
I am returning true from the destroy does make update_attribute a little dangerous but I am catching exceptions at the ApplicationController level as well, so works well for us.
Given below are two models associated with one to many relationship and it works fine
class User < ActiveRecord::Base
has_many :events, dependent: :destroy
end
but when I associate them one to one
class User < ActiveRecord::Base
has_one :event, dependent: :destroy
end
It gives me the following error
undefined method `build' for nil:NilClass
Events Controller
class EventsController < ApplicationController
def new
#event = current_user.event.build
end
def create
#event = current_user.event.build(event_params)
if #event.save
redirect_to #event
else
render 'new'
end
end
private
def event_params
params.require(:event).permit(:date, :time, :venue)
end
end
The reason it is throwing the error is because there is no event for that user. While that is how to build through association for a has_many relationship, it doesn't work for has_one. See the documentation on has_one where they say that calling .build will not work.
Instead use #event = current_user.create_event
Adding a has_one relationship will give you the following methods:
association(force_reload = false)
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
For has_one associations, Rails provides a special set of methods for building the association (documentation)
build_event
create_event
Both of these take a hash of attributes just like the build method of a has_many association does.
In your case, change current_user.event.build to current_user.build_event and current_user.event.build(event_params) to current_user.build_event(event_params)
How can I redirect to the next lesson that does not have userLesson (problem is lessons belongs to a course through a chapter)
Models:
class Course
has_many :lessons, through: :chapters
end
class Lesson
belongs_to :chapter
has_one :lecture, through: :chapter
end
class User
has_many :user_lessons
end
class UserLesson
#fields: user_id, lesson_id, completed(boolean)
belongs_to :user
belongs_to :lesson
end
class Chapter
has_many :lessons
belongs_to :lecture
end
here user_lessons_controller:
class UserLessonsController < ApplicationController
before_filter :set_user_and_lesson
def create
#user_lesson = UserLession.create(user_id: #user.id, lession_id: #lesson.id, completed: true)
if #user_lesson.save
# redirect_to appropriate location
else
# take the appropriate action
end
end
end
I want to redirect_to the next lesson that has not the UserLesson when saved. I have no idea how to do it as it belongs_to a chapter. Please help! Could you please help me with the query to write...
Here is the answer for your question:
Inside your user_lessons_controller:
def create
#user_lesson = UserLession.create(user_id: #user.id, lession_id: #lesson.id, completed: true)
if #user_lesson.save
#You have to determine the next_lesson object you want to redirect to
#ex : next_lessons = current_user.user_lessons.where(completed: false)
#This will return an array of active record UserLesson objects.
#depending on which next_lesson you want, you can add more conditions in `where`.
#Say you want the first element of next_lessons array. Do
##next_lesson = next_lessons.first
#after this, do:
#redirect_to #next_lesson
else
#redirect to index?? if so, add an index method in the same controller
end
end
This code will only work if you define show method in your UserLessonsController and add a show.html in your views.
Also, in config/routes.rb, add this line : resources :user_lessons.
Lets say I have two ActiveRecord classes:
class Student < ActiveRecord::Base
has_and_belongs_to_many :guardians
end
class Guardian < ActiveRecord::Base
has_and_belongs_to_many :students, inverse_of: :guardian
before_validation :generate_username, on: :create
def generate_username
count = students.count
self.username = 'g'+students.first.admission_no
end
end
And the create function of my GuardiansController:
def create
#student = Student.find(params[:student_id])
#guardian = #student.guardians.build(guardian_params)
flash.now[:success] = 'Guardian was successfully created.' if #guardian.save
respond_with(#guardian)
end
The generate_username function of the Guardian ActiveRecord Class fails because students returns an empty Association CollectionProxy.
How can I get the owner (student) of a Guardian before it is saved??
I'm trying to add a 'Collections' model to group Posts so that any user can add any Post they like to any Collection they've created. The Posts will have already been created by a different user. We are just letting other users group these posts in their own Collections. Basically like bookmarking.
What is the cleanest, and most Rails-ey-way of doing this?
I've created the model and run through the migration and what not. Also I've already created proper views for Collection.
rails g model Collection title:string user_id:integer
collections_controller.rb
class CollectionsController < ApplicationController
def index
#collections = current_user.collections.all
end
def show
#collection = Collection.all
end
def new
#collection = Collection.new
end
def create
#collection = current_user.collections.build(collection_params)
if #collection.save
redirect_to #collection, notice: 'saved'
else
render action: 'new'
end
end
def update
end
private
def collection_params
params.require(:collection).permit(:title)
end
end
collection.rb
class Collection < ActiveRecord::Base
belongs_to :user
has_many :posts
validates :title, presence: true
end
post.rb
has_many :collections
It seems like has_many or has_and_belongs_to_many associations are not correct? Should I be creating another model to act as an intermediary to then use
has_many :collections :through :collectionList?
If my association is wrong, can you explain what I need to change to make this work?
Also the next part in this is since this is not being created when the Post or Collection is created, I'm not sure the best way to handle this in the view. What is the best way to handle this, keeping my view/controller as clean as possible? I just want to be able to have a button on the Post#Show page that when clicked, allows users to add that post to a Collection of their own.
In such case you should use or has_and_belongs_to_many or has_many :through association. The second one is recommended, because it allows more flexibility. So now you should:
Create new model PostsCollections
rails g model PostsCollections post_id:integer collection_id:integer
and migrate it
Set correct model associations:
Something like:
class Post < ActiveRecord::Base
has_many :posts_collections
has_many :categories, through: :posts_collections
end
class Collection < ActiveRecord::Base
has_many :posts_collections
has_many :posts, through: :posts_collections
end
class PostsCollections < ActiveRecord::Base
belongs_to :post
belongs_to :collection
end
Then you'll be able to use
#collection.first.posts << #post
And it will add #post to #collection's posts
To add a post to a collection from view
Add a new route to your routes.rb, something like:
resources :collections do # you should have this part already
post :add_post, on: :member
end
In your Collections controller add:
def add_post
#post = Post.find(params[:post_id])
#collection = Collection.find(params[:id])
#collection.posts << #post
respond_to do |format|
format.js
end
end
As for views, you'll have to create a form to show a collection select and button to add it. That form should make POST method request to add_post_collection_path(#collection) with :post_id parameter.
You can read more explanations of how rails associations work in Michael Hartl's tutorial, because that subject is very wide, and can't be explained with short answer.