Access childrens using where condition for nested attributes - ruby

I have the following relationship set, dashboard filter values have a column called filter_type which can have value 1 or 0.
class DashboardFilterValue < ApplicationRecord
belongs_to :dashboard_filter
end
class DashboardFilter < ApplicationRecord
has_many :dashboard_filter_values, dependent: :destroy
accepts_nested_attributes_for :dashboard_filter_values
before_save :check_parameter_length
def check_parameter_length
Rails.logger.info self.dashboard_filter_values.inspect #prints the ActiveRecord::Associations::CollectionProxy
Rails.logger.info self.dashboard_filter_values.where(:filter_type => 0) #does not print anything
end
end
In the before_save callback,
When I use self.dashboard_filter_values.inspect, this prints
ActiveRecord::Associations::CollectionProxy.
But self.dashboard_filter_values.where(:filter_type => 0) does not print anything, even when there are records which satisfy the condition.
In the before_save callback, how can I use the where condition to filter values that I want.
Any help in this would be really great. Thanks.

I believe this is not working because of the before_save action. When you use where it is performing a database query, but because you are querying the database before it saves, nothing is returned.
I would say you have 2 options:
Convert it to an after_save
Use Enumerable#select instead:
Rails.logger.info self.dashboard_filter_values.select { |filter| filter.filter_type == 1 }

Related

Best way to count ActiveRecord child custom properties?

If I have a shopping cart, and in it is produce (where each produce has a custom expired property), what is the best way to count the expired property? -- is there a better way than this count_expired?
class Cart < ActiveRecord::Base
has_many :produce, :dependent => :delete_all
def count_expired
count = 0
self.produces.each do | produce |
if produce.expired
count ++
end
end
return count
end
end
class Produce < ActiveRecord::Base
belongs_to :cart, class_name: "Cart", touch: true
def expired
return <true or false logic>
end
end

Update on has_many should update attribute on the parent class

I have a User model which has_many :scores.
If I add a :score to the user, the user should do a recalculation of the playcounter:
class User < ActiveRecord::Base
has_many :scores, inverse_of: :user
accepts_nested_attributes_for :scores
attr_accessible :level
before_save: set_levels
def set_levels
self.level = calculate_level
end
def calculate_level
self.scores.count
end
end
When I add a score via User.score.create(:time => 10) the score get's saved but the level does not get updated.
How can I rerun the set_levels if a child was attached?
(it's not a countercache column, method it's more complex, just sketched here)
many thanks
You need to add a callback to the Score model, perhaps an after_commit on the score model to tell the parent User to recalculate the level.
class Score < ActiveRecord::Base
after_commit do
# can just call touch, as before_save will calc
user.touch
end
end
Now, that is only if you want to recalculate on every score, and synchronously.
More often I will drop a message, such as to resque/sidekiq, to do this, so my score create/updates are fast.
class Score < ActiveRecord::Base
after_commit do
UpdateUserLevelWorker.perform_async(user_id)
end
end
class UpdateUserLevelWorker
include Sidekiq::Worker
def perform(user_id)
# can just call touch, as before_save will calc
User.find(user_id).touch
end
end
I've got it working with
after_create :set_level
def set_level
self.update_attribute :level, calculate_level
end

Using decrement_counter in after_update callback is subtracting 2?

I have three models. Employer, User, Job.
class Employers
has_many :jobs
has_many :users, through: :jobs
end
class User
has_many :jobs
end
class Job
belongs_to :user
belongs_to :employer
end
The Job model has a boolean column named "current". An employers user count is derived by counting all the associated jobs marked 'current'.
I opted to rolled my own cache counter, rather than use active records.
Im using a before filter in the Job model to either increment or decrement a users_count in the Employer model. The increment works as expected, but no matter how I tweak the code...the decrement drops the count by a value of 2.
Im sure I can clean these methods up a bit...there might be some redundancy.
1 Why is the decrement subtracting 2 instead of 1?
2 Can the active record cache counter handle logic like this?
class Job
before_destroy :change_employer_users_counter_cache_after_destroy
before_create :change_employer_users_counter_cache_after_create
before_update :change_employer_users_counter_cache_after_update
def change_employer_users_counter_cache_after_create
Operator.increment_counter(:users_count, self.operator_id) if self.current == true
end
def change_employer_users_counter_cache_after_update
if self.current_changed?
if self.current == true
Operator.increment_counter(:users_count, self.operator_id)
else
Operator.decrement_counter(:users_count, self.operator_id)
end
end
end
def change_employer_users_counter_cache_after_destroy
Operator.decrement_counter(:users_count, self.operator_id)
end
end
the gem "counter_culture" handled this very nicely...and cleaned up my code.

Rails 3. Decide on save if the object should be saved or not

iam just asking myself, whats the best solution for my problem.
Here are my models:
class Product < ActiveRecord::Base
has_many :prices, :class_name => "ProductPrice"
accepts_nested_attributes_for :prices
end
class ProductPrice < ActiveRecord::Base
belongs_to :product
end
The controller
def create
#product = Product.new(params[:product])
#product.save
...
end
What i want to do is to prevent all ProductPrices from being saved when product_price.value == nil or product_price.value == 0.0
before_save hook in ProductPrice. return false will rollback the whole transaction, thats not what i want to do. i just want to "kick" all prices with value == 0 or value == nil
first kick all price_params from params[...] and than call Product.new(params[:product]) seems not to be the rails way eighter...
after Product.new(params[:product]) iterate over all prices and delete them from the array. but the logic should be in my models right? i just dont want to repeat myself on every controller that creates new prices...
can someone tell me the best solution for that? whats the rails way?
thanks!
What you want it called a validation hook, something like this:
class ProductPrice < ActiveRecord::Base
belongs_to :product
validates :value, :numericality => {:greater_than => 0.0 }
end
See http://guides.rubyonrails.org/active_record_validations_callbacks.html for other ways you may want to do this with finer control.
To avoid adding these invalid prices in the first place, you can remove them from the nested attributes hash like this:
class Product < ActiveRecord::Base
def self.clean_attributes!(product_params)
product_prices = product_params['prices'] || []
product_prices.reject!{|price| price['value'].to_f == 0 rescue true }
end
end
Product.clean_attributes!(params[:product])
Product.new(params[:product])

Many-to-Many Uniqueness Constraint Test Not Working

I have a many-to-many relationship with a join table in my Rails application. I'm using the has_many :through idiom in my models. To keep things simple, lets call my first class Student, my second class Course, and the join table class Enrollment (which contains fields student_id and course_id). I want to make sure that a given Student is associated with a given Course at most once (i.e. the {student_id, course_id} tuple should be unique in the enrollment table).
So I have a migration a that enforces this uniqueness.
def change
add_index :enrollments, [:student_id, :course_id], :unique => true
end
In addition my model classes are defined as such:
class Student < ActiveRecord::Base
has_many :enrollments
has_many :courses, :through => :enrollment
end
class Course < ActiveRecord::Base
has_many :enrollments
has_many :students, :through => :enrollment
end
class Enrollment < ActiveRecord::Base
belongs_to :student
belongs_to :course
validates :student, :presence => true
validates :course, :presence => true
validates :student_id, :uniqueness => {:scope => :course_id}
end
In a rails console, I can do the following:
student = Student.first
course = Course.first
student.courses << course
#... succeeds
student.courses << course
#... appropriately fails and raises an ActiveRecord::RecordInvalid exception
In my RSpec test, I do the exact same thing and I get no exception with the following code:
#student.courses << #course
expect { #student.courses << #course }.to raise_error(ActiveRecord::RecordInvalid)
And so my test fails and reports:
expected ActiveRecord::RecordInvalid but nothing was raised
What's going on here? What could I be doing wrong? How do I fix it?
Rails uses model level validation, if you want strict checking for uniquiness you need to use database level - foreign keys for example. But in this case you need to catch exceptions from database connector.
This is strange because in my code (very similar to your) validation for unique raises exception.
There's a couple of things here that could be happening:
#courses has changed between uses.
#student has changed between uses.
By using let you'll protect these values from changing between expectations.
let(:course) { Course.first }
let(:student) { Student.first }
subject{ student.courses << course << course }
it { should raise_error(ActiveRecord::RecordInvalid) }
Or, there could just be something wrong with your code :)

Resources