Simplifying or improving a method - ruby-on-rails

The following code is executed before the model is saved. It checks a has_many association for change before looping through and setting a value on each. It checks through each answer option whether the correct_answer is changed on any of the answers. If so, it looks for which was changed and was true.
if self.answer_options.select{|a| a.correct_answer_changed?}.any?
self.answer_options.each do |answer_option|
if answer_option.correct_answer_changed? && !answer_option.correct_answer_was
answer_option.correct_answer = true
else
answer_option.correct_answer = false
end
end
end
How could I simplify or improve this method?

Assuming the code you have shared is working, I would dry it up a bit as..
self.answer_options.map{|a| a if a.correct_answer_changed?}.compact.each do |answer_option|
answer_option.toggle(:correct_answer)
end

answer_options.each do |answer_option|
answer_option.correct_answer =
answer_option.correct_answer_changed? && !answer_option.correct_answer_was
end
if answer_options.all?{|answer_option| answer_option.correct_answer == false}
answer_options.each{|answer_option| answer_option.correct_answer == nil}
end

Related

Rails 5 - iterate until field matches regex

In my app that I am building to learn Rails and Ruby, I have below iteration/loop which is not functioning as it should.
What am I trying to achieve?
I am trying to find the business partner (within only the active once (uses a scope)) where the value of the field business_partner.bank_account is contained in the field self_extracted_data and then set the business partner found as self.sender (self here is a Document).
So once a match is found, I want to end the loop. A case exists where no match is found and sender = nil so a user needs to set it manually.
What happens now, is that on which ever record of the object I save (it is called as a callback before_save), it uses the last identified business partner as sender and the method does not execute again.
Current code:
def set_sender
BusinessPartner.active.where.not(id: self.receiver_id).each do |business_partner|
bp_bank_account = business_partner.bank_account.gsub(/\s+/, '')
rgx = /(?<!\w)(#{Regexp.escape(bp_bank_account)})?(?!\‌​w)/
if self.extracted_data.gsub(/\s+/, '') =~ rgx
self.sender = business_partner
else
self.sender = nil
end
end
end
Thanks for helping me understand how to do this kind of case.
p.s. have the pickaxe book here yet this is so much that some help / guidance would be great. The regex works.
Using feedback from #moveson, this code works:
def match_with_extracted_data?(rgx_to_match)
extracted_data.gsub(/\s+/, '') =~ rgx_to_match
end
def set_sender
self.sender_id = matching_business_partner.try(:id) #unless self.sender.id.present? # Returns nil if no matching_business_partner exists
end
def matching_business_partner
BusinessPartner.active.excluding_receiver(receiver_id).find { |business_partner| sender_matches?(business_partner) }
end
def sender_matches?(business_partner)
rgx_registrations = /(#{Regexp.escape(business_partner.bank_account.gsub(/\s+/, ''))})|(#{Regexp.escape(business_partner.registration.gsub(/\s+/, ''))})|(#{Regexp.escape(business_partner.vat_id.gsub(/\s+/, ''))})/
match_with_extracted_data?(rgx_registrations)
end
In Ruby you generally want to avoid loops and #each and long, procedural methods in favor of Enumerable iterators like #map, #find, and #select, and short, descriptive methods that each do a single job. Without knowing more about your project I can't be sure exactly what will work, but I think you want something like this:
# /models/document.rb
class Document < ActiveRecord::Base
def set_sender
self.sender = matching_business_partner.try(:id) || BusinessPartner.active.default.id
end
def matching_business_partners
other_business_partners.select { |business_partner| account_matches?(business_partner) }
end
def matching_business_partner
matching_business_partners.first
end
def other_business_partners
BusinessPartner.excluding_receiver_id(receiver_id)
end
def account_matches?(business_partner)
rgx = /(?<!\w)(#{Regexp.escape(business_partner.stripped_bank_account)})?(?!\‌​w)/
data_matches_bank_account?(rgx)
end
def data_matches_bank_account?(rgx)
extracted_data.gsub(/\s+/, '') =~ rgx
end
end
# /models/business_partner.rb
class BusinessPartner < ActiveRecord::Base
scope :excluding_receiver_id, -> (receiver_id) { where.not(id: receiver_id) }
def stripped_bank_account
bank_account.gsub(/\s+/, '')
end
end
Note that I am assigning an integer id, rather than an ActiveRecord object, to self.sender. I think that's what you want.
I didn't try to mess with the database relations here, but it does seem like Document could include a belongs_to :business_partner, which would give you the benefit of Rails methods to help you find one from the other.
EDIT: Added Document#matching_business_partners method and changed Document#set_sender method to return nil if no matching_business_partner exists.
EDIT: Added BusinessPartner.active.default.id as the return value if no matching_business_partner exists.

What is wrong in this ruby on rails method?

My syntax error is solved, but now I've got another problem.
I have a model User and Post. Also i've created the additional model which is called Like(for likes/dislikes system). So, i wrote a method to check if the copy of the model Like has the given 'post_id' and 'user_id' simulteniously, then the given Post was liked by the given User. Here's the code of my method
def licked(p,u)#p - post, u - user
if Like.all.empty? then return false
else
if (Like.where(post_id: p.id).empty?)
return false
else
posts=Like.where(post_id: p.id)
if ((posts.length<2) && (posts[0].user_id==u.id) && (posts[0].action!=nil))==true then return true
else
if ((posts.length<2) && (posts[0].user_id!=u.id)&& (posts[0].action!=nil)) then return false
else posts.each do |i|
if (i.user_id==u.id&& (posts[0].action!=nil)) then return true
end
end
end
end
end
end
end
Sorry, if there are many if's, i did not find any other way to relize it, and anyway it's not working. So i'm asking for your help: either to hange the existing code or making it other way, thanks.
You don't need ANY of that logic.
def liked(post, user)
Like.where(user_id: user.id, post_id: post.id).where.not(action: nil).exists?
end

Rails : Return an object to show user's profile completeness

I have a User Model and would like to return to the controller and view what parts of the user's profile are complete and what parts are not.
So it would return for example an object "profile_completeness" which can be used like so :
#user.profile_completeness.personal_info
Which would be true if the particular part of the profile was filled.
What I tried :
def profile_completeness
if self.name.present? && self.email.present?
profile_completeness.personal_info = true
end
return profile_completeness
end
I got the error :
SystemStackError: stack level too deep
Am I going about this the right way? If not, what do you think is the best
You can't call set the .personal_info=true on the method itself. In such situation I'd just use either Hashes.
def profile_completeness
ret = Hash.new
if self.name.present? && self.email.present?
ret[:personal_info] = true
end
ret
end
Or even better I'd prefer different method for each of the 'completeness' part:
def personal_info_complete?
....
end

Ruby/Rails: best way of checking if a User has an Answer for a Q

So I have a method in my User model that tries to determine if a user has answered a Question. A User has_many Answers, and a Question has_many Answers (through Choices). Here's what I have so far:
def has_answered(question)
has_answered_question = false
answers.each { |answer|
has_answered_question = true if answer.question == question
}
has_answered_question
end
I was wondering if there's any way to clean this up. It seems like there should be someway to do this without a boolean, and possibly in less lines than I'm currently doing it. The more suggestions / ways of doing this, the better. All suggestions welcome.
answers.any? { |answer| answer.question == question }
or
Answer.where('user_id = ?', user.id).where('question_id = ?', question.id).limit(1).count > 0

Rails validate association only when loaded

I have an activity model which has_many participants and I'd like to ensure that a participant always exists when updating an activity and its participants. I have the following method in my activity model which does the trick:
def must_have_participant
if self.participants.size == 0 || self.participants.size == self.participants.to_ary.find_all{ |p| p.marked_for_destruction? }.count
self.errors[:base] << I18n.t(:msg_activity_must_have_participant)
end
end
The problem is that the participants are lazy loaded if I'm simply updating the activity on its own which I'd like to avoid. I've tried the following alternative, however, loaded? returns false when removing all participants using the :_destroy flag.
def must_have_participant
if self.new_record? || self.participants.loaded?
if self.participants.size == 0 || self.participants.size == self.participants.to_ary.find_all{ |p| p.marked_for_destruction? }.count
self.errors[:base] << I18n.t(:msg_activity_must_have_participant)
end
end
end
Is there an alternative to loaded? that I can use to know whether the participants are going to be updated?
I did something like this in a recent validation that I created. I searched for the original record and checked the original value against the new value. No guarantees my code will work for you but here is my code for your application:
orig_rec = self.find(id)
if participant_ids.size != orig_rec.participant_ids.size
Note that I checked the size of participant_ids instead of fetching all the participant records and checking the size of them. That should be more efficient.
I don't know if there is some kind of built in function to do this or not in ruby, I'll be curious to see what someone who is more rails specific may suggest.
For reference I've amended the method like so:
def must_have_participant
if self.new_record? || self.association(:participants).loaded?
if self.participants.size == 0 || self.participants.size == self.participants.select{ |p| p.marked_for_destruction? }.size
self.errors[:base] << I18n.t(:msg_must_have_participant)
end
end
end

Resources