Custom relationship similar to dependent destroy - ruby-on-rails

I would like to implement certain relationship between 2 models.
I have 2 models: quiz and question that have many-to-many relationship.
Quiz model have quiz_flag and question model have question_flag.
What I want to happen is when quiz_flag is changed to true, every question that is in direct relationship (basically every question that is contained within that quiz), should also change question_flag to true.
Logic is similar to dependent: :destroy, but it's a custom function that I want to trigger when quiz_flag becomes true.
But how do I specifically do that?

You could just add additional logic to whatever form/action is responsible for setting quiz.
I.e.:
if params[:quiz_flag] #if the quiz_flag params is set to true.
#quiz.questions.update_all(question_flag: true)
end
Or if it's for multiple controllers, you could use callbacks:
Quiz Model:
before_save :some_method #will work before object is saved
(works with both create and update, if you just want update use before_update)
def some method
if self.quiz_flag == true
self.questons.update_all(question_flag:true)
end
end
I would caution you on using callbacks though. It can lead to some messy code that will be difficult to test for later.

You can use the callback :before_update inside your model.
I'd do something like this:
class Quiz < ApplicationRecord
before_update :update_question_flags, :if => :question_flag_changed?
def update_question_flags
self.questons.update_all(question_flag:true)
end
end

Related

callback on associated has_many not working

Seems like i’ve gone back to basics and am missing something… ..
I have two models User and Lists. User has many lists and lists belongs to User… When we remove the user from the list, i.e. the lists user_id == nil, we can’t seem to catch the update through a callback like after_update or after_commit.
class List < ApplicationRecord
after_update :check_user
belongs_to :user
private
def check_user
binding.pry
if user_id.blank?
end
end
end
we even tried before_save but it doesn’t seem like the model sees the change. Are we missing something obvious??
What method are you using to update the record? Because there are methods that DO NOT trigger callbacks like update_column, update_attribute and update_all

Rails 5 set has_one association with strong params

I am developing a Rails 5 application in which I encountered the following difficulty.
I've got two models, let's say Kid and Toy, which are in one-to-one relationship like this:
class Kid < ActiveRecord::Base
has_one :toy
end
class Toy
belongs_to :kid, optional: true
end
So the toys can belong to zero or one kid, and from day to day it can change - it is always another kid's responsibility to look after a certain toy. Now, when I edit a toy, changing its kid is easy as can be: I just send kid_id in the strong params to update the record:
params.require(:toy).permit(:name, :type, :kid_id)
But recently, I was asked to implement the changing feature from the other way too, that is, when editing a kid, I should do something like this:
params.require(:kid).permit(:name, :age, :toy_id)
The problem is that - while belongs_to works with association_id and even has_many provides association_ids getter and setter - has_one relationship has nothing like this. What is more, has_one association gets saved the moment I call association = #record. So I simply cannot set it by sending the toy_id in the strong parameters.
I could do something like #kid.update(kid_params); #kid.toy = #toy on the controller level, but that would rather bring model logics to my controller, not to mention that I want to check if the newly assigned toy did not belong to another kid, which I imagine as some kind of validation.
The best I could come up with was to define some rails-like methods for Kid class like
def toy_id
#toy_id = toy.id unless defined?(#toy_id)
#toy_id
end
def toy_id_changed?
toy_id != toy.id
end
and set a validation and a before_commit callback
validate if: -> { toy_id.present? && toy_id_changed? } do
errors.add :toy_id, :other_has_it if new_toy.kid_id.present? && new_toy.kid_id != id
end
before_commit if: -> { toy_id_changed? } do
toy = new_toy
end
private
def new_toy
#new_toy ||= Toy.find(toy_id)
end
So far it works as expected, and now I can send toy_id in the strong params list to update a kid, and it updates the toy association if -
and only if - there is no validation error. I have even put it in a concern to be nice and separated.
My question is: isn't there a rails way to do this? haven't I reinvented the wheel?
Thanks in advance!

Cycle through object has_many association and remove certain one

Im creating an object (recipe_change) that has many ingredient_changes. However I want to run my own validation of sort and prevent the creation of ingredient_changes if they dont pass. I started with a validation in the ingredient_changes but with that if I added an error the valid? would prevent the change from being submitted if one of the many ingredient_changes wasnt valid...which I dont want. So then I tried the following in my create method in the recipe_change controller and this almost did the job except once it deleted one it would break the loop and not do the rest:
#recipe_change.ingredient_changes.each do |change|
if ...long conditional statement...
#recipe_change.ingredient_changes.delete(change)
end
end
Is there a function like the array .reject! that works for associations like this. At this point in the code they are not saved to the database and Im trying to not save them to the database if they dont meet my condition.
You can use reject_if to filter out invalid ingredient_changes in recipe_change model like this:
class RecipeChange < ActiveRecord::Base
has_many :ingredient_changes, reject_if: :invalid_ingredient_change?
def invalid_ingredient_change?(attributes)
end
end
See: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Model column that depends on other columns

I have a gamification app that has four types of points, and the sum of all these kinds is the total points for a user, I want to be able to do sum and scopes on that column, so I think I should have it as a column in the DB.
scope :points_rank, -> { order(points: :desc) }
I was using a before_save for adding all four point types and storing it in points, but now I'm using a gem that does increment to these types of points, so when it updates those values, the before_save is not called, hence not updating the points value as expected.
What is the correct ActiveRecord callback to be using instead of before_save, or what else could I be doing to keep the column updated.
Try using the after_touch callback instead.
after_touch callback is triggered whenever an object is touched.
So, whenever point type changes, it should update the points.
First of all, counter_culture seems to be a way to enhance the counter_cache functionality of rails...
Used to cache the number of belonging objects on associations. For example, a comments_count column in a Post class that has many instances of Comment will cache the number of existent comments for each post.
It might not be exactly what you want, judging from your question.
Okay I get it. You're using points in your User model to create a "cached" column which can be used for wider application functionality. Okay that's cool...
--
Your setup, then, will look something like this (you were manually setting the counter_cache column, and now the gem handles it):
#app/models/user.rb
class User < ActiveRecord::Base
counter_cache :points
end
#app/models/point.rb
class Point < ActiveRecord::Base
belongs_to :user, counter_cache: true
end
The question is then that when you update the points model, you need to be able to update the "cached" column in the users model, now without any callbacks.
What is the correct ActiveRecord callback to be using instead of before_save
I'm presuming you're calling before_save on your User model (IE adding the associated data and putting the points column?
If so, you should try using a callback on the Point model, perhaps something like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :points
end
#app/models/point.rb
class Point < ActiveRecord::Base
belongs_to :user, inverse_of: :points
after_commit :update_user
private
def update_user
if user?
user.update(points: x + y + z)
end
end
end
--
Oberservers
If you have real problems, you could look at ActiveRecord observers.
Here's an answer I wrote about it: Ruby On Rails Updating Heroku Dynamic Routes
Whether this will trigger without any callbacks is another matter, but what I can say is that it will work to give you functionality you may not have had access to otherwise:
#config/application.rb (can be placed into dev or prod files if required)
config.active_record.observers = :point_observer
#app/models/point_observer.rb
class PointObserver < ActiveRecord::Observer
def before_save(point)
#logic here
end
end
A good way to test this would be to use it (you'll have to use the rails-observers gem) with different methods. IE:
#app/models/point_observer.rb
class PointObserver < ActiveRecord::Observer
def initialize(point)
#if this fires, happy days
end
end

How can I access ActiveRecord Associations in class callbacks in rails?

Updated
Appears to be a precedence error and nothing to do with the question I originally asked. See discussion below.
Original question
Is it possible to use active record associations in callbacks? I've tested this code in the console and it works fine as long as it isn't in a callback. I'm trying to create callbacks that pull attributes from other associated models and I keep getting errors of nil.attribute.
If callbacks are not the correct approach to take, how would one do a similar action in rails? If the associations are simple, you could use create_association(attributes => ), but as associations get more complex this starts to get messy.
For example...
class User < ActiveRecord::Base
belongs_to :b
before_validation_on_create {|user| user.create_b} #note, other logic prevents creating multiple b
end
class B < ActiveRecord::Base
has_many :users, :dependent => destroy
after_create{ |b| b.create_c }
has_one :c
end
class C < ActiveRecord::Base
belongs_to :b
after_create :create_alert_email
private
def create_alert_email
self.alert_email = User.find_by_b_id(self.b_id).email #error, looks for nil.email
end
end
Off course associations are available in your callbacks. After all, the create_after_email is simply a method. You can even call it alone, without using a callback. ActiveRecord doesn't apply any special flag to callback methods to prevent them from working as any other method.
Also notice you are running a User#find query directly without taking advantage of any association method. An other reason why ActiveRecord association feature should not be the guilty in this case.
The reason why you are getting the error should probably searched somewhere else.
Be sure self.b_id is set and references a valid record. Perhaps it is nil or actually there's no User record with that value. In fact, you don't test whether the query returns a record or nil: you are assuming a record with that value always exists. Are you sure this assumption is always statisfied?

Resources