rails group collection by association - ruby-on-rails

I'm have a few collections in a controller like so:
def show
#cave = Cave.includes(cubs: :mamabear).where(id: params[:id]).first
#cubs = #cave.cubs
#papabear = Papabear.first
#dens = Den.where(papabear_id: #papabear.id)
end
and now I'm trying to sort cubs by dens so I can display the cubs with
#dens.each do |d|
d.cubs
end
so I wrote the following:
def show
....
#dens.each do |den|
den.cubs = [] ## < --- Den does not have an association with Cub ##
#cubs.each do |cub|
den.cubs << cub if cub.mamabear.den_id == den.id
end
end
#reject dens if they don't have cubs
#dens = #dens.reject { |den| den.cubs.all?(&:blank?) }
end
But now I'm getting an undefined method 'cubs for Den' error because Den doesn't have an association with Cub. How do I assign an array of Cubs to each Den without an association?

1: I would create a standard association of Cubs for den. Probably a standard has_many On dens, so each cub has a den_id.
You're messing about re-inventing the wheel, otherwise.
2: you'll probably find that the undefined method is 'cubs=' as opposed to 'cubs'. It's an important distinction as it says what the code is doing when the error is thrown.
3: if you really want to ignore point 1 and make your own which fills an arbitrary attribute from the controller, you can add this to the Den model.
attr_accessor :cubs

Association is the best way to handle such scenarios if you want to fetch those cubs belonging to den at multiple places. if you dont want to implement association. you can try this solution
#den_cubs = []
#dens.each do |den|
cub_for_den= {} #here we are initializing hash each time for new den
#cubs.each do |cub|
cub_for_den[cub.id] = cub if cub.mamabear.den_id == den.id
end
#den_cubs << cub_for_den #put the hash in array
end
#den_cubs = #den_cubs.reject { |dc| dc.blank? }
on the show page you can do this
#den_cubs.each do |dc|
dc.each do |k,v|
# now here you can display all attributes for the cubs
end
end

Have you considered using a "has many through"-association in regards to cubs -> dens and dens -> cubs?
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Examples:
class Den
has_many :mamabears
has_many :cubs, through: :mamabears
end
class Cup
belongs_to :mamabear
has_one :den, through: :mamabear
end
Then you should be able to do something like:
den.cups #=> [<Cup ...>]
cup.den #=> <Den ...>

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.

Getting objects from active record in rails

I want to fetch all the items being followed and the for each item I want to fetch all of it's articles in an array to pass it to the view partial.
But I am not receiving objects rather I am receiving Active Record relation
Here is my code
#row=[]
#followed_blogs = Follow.where("followable_type == ?","Blog").where(follower_id: current_user.id)
#followed_blogs.each do |blog|
#row << Article.where("blog_id == ?",blog.followable_id)
end
#articles = #row.sort! {|a,b| a.created_at <=> b.created_at}
#followed_blogs = Follow.
where(followable_type: "Blog").
where(follower_id: current_user.id).
includes(:articles)
#row = #followed_blogs.map(&:articles)
#articles = #row.sort! {|a,b| a.created_at <=> b.created_at}
This might work better, assuming you've got the relations set correctly between Follow and Article.
class Follow < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :follow, as: blog
end
I think that's right, you'll have to tweak it. http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
With the polymorphic association set up properly, the first line becomes:
#followed_blogs = Blog.
where(follower_id: current_user.id).
includes(:articles)
And if User has the correct association (has_many :blogs or something), it can even become
#articles = current_user.blogs.includes(:articles).
map(&:articles).
sort! {|a,b| a.created_at <=> b.created_at}
#f_blogs = Follow.where('followable_type == ?',"Blog").where('follower_id == ?', current_user.id).pluck('followable_id')
#articles = Article.having(blog_id: #f_blogs).group('id')

Method to return a collection - Ruby

Say, I have a method called posted_listings, which is supposed to run an ActiveRecord query and return a collection of User.listings where posted: true, with posted? being a Listing class method. So far I have been doing:
class Bar < ActiveRecord::Base
def posted_listings
posted_listings = []
listings.each { |listing| posted_listings << listing if listing.posted? }
posted_listing
end
end
but each time this query runs I start feeling really bad about my skills (or lack of thereof). What is the most efficient way to return a collection of posted listings?
Edit:
posted? is not an attribute, its a class method:
class Listing < ActiveRecord::Base
def posted?
true if quantity >= 1 && has_sellers?
end
end
def has_sellers?
sellers.count >=1 #association to Seller
end
I would recommend adding a scope to your Listing model like this:
scope :posted, -> { where(posted: true) }
Then you can get all posted listings like this:
#user.listings.posted
You can learn more about scopes here if you are interested.
UPDATE
Try this scope instead:
def self.posted
joins(:sellers)
.where('posted = ? AND quantity > ?', true, 0)
.group('listings.id')
.having('COUNT(sellers.id) > ?', 0)
end
Your question is not so clear for me.
You may try:
User.listings.where(posted: true)
to get all users' posted Listings.
Or, saying #useris an User instance:
#user.listings.where(posted: true)
to get posted Listings from an specific user.

Rails How can one query association definitions

I have a lot of dynamic code which keeps complex relations in a string.
ex:
"product.country.continent.planet.galaxy.name"
How can I check if these relations exist?
I want a method like the following:
raise "n00b" unless Product.has_associations?("product.country.planet.galaxy")
How could I implement this?
Try this:
def has_associations?(assoc_str)
klass = self.class
assoc_str.split(".").all? do |name|
(klass = klass.reflect_on_association(name.to_sym).try(:klass)).present?
end
end
If these are active record associations, here's how you can do it:
current_class = Product
has_associations = true
paths = "country.planet.galaxy".split('.')
paths.each |item|
association = current_class.reflect_on_association( item )
if association
current_class = association.klass
else
has_associations = false
end
end
puts has_association
And this will tell you if this specific path has all the associations.
If indeed you are storing the AR associations in a string like that, this code placed in an initializer should let you do what you want. For the life of me I can't quite figure out why you'd want to do this, but I trust you have your reasons.
class ActiveRecord::Base
def self.has_associations?(relation_string="")
klass = self
relation_string.split('.').each { |j|
# check to see if this is an association for this model
# and if so, save it so that we can get the class_name of
# the associated model to repeat this step
if assoc = klass.reflect_on_association(j.to_sym)
klass = Kernel.const_get(assoc.class_name)
# alternatively, check if this is a method on the model (e.g.: "name")
elsif klass.instance_method_already_implemented?(j)
true
else
raise "Association/Method #{klass.to_s}##{j} does not exist"
end
}
return true
end
end
With this you'll need to leave off the initial model name, so for your example it would be:
Product.has_associations?("country.planet.galaxy")

ActiveRecord: Can I copy associations?

Is there a way to copy the associations of one model to another...
template_model = MyModel.find(id)
new_model = template_model.clone
new_model.children << template_model.children # I want to *copy* children
...such that I copy the children from the template to the new model? (In fact this code moves children from the template to the new model).
I know I can do it manually be looping, but is there are more succinct way?
Thanks
The problem is that you are cloning the template, but not cloning it's children. Try something like:
template_model = MyModel.find(id)
new_model = template_model.clone
new_model.children << template_model.children.collect { |child| child.clone }
Add this some where is under /lib. For example clone_deep.rb.
module CloneDeep
def clone_deep
kopy = clone
self.class.reflect_on_all_associations.each do |association|
next if association.macro == :belongs_to
cloned_object = case association.macro
when :has_many
self.send(association.name).collect { |item| item.clone_deep }
when :has_one
self.send(association.name) && self.send(association.name).clone_deep
else
clone
end
kopy.send("#{association.name}=", cloned_object)
end
return kopy
end
end
Create new initializer under config/initializers/ folder. Inside this file paste
ActiveRecord::Base.send(:include, CloneDeep)
Now you be able to clone model with all it's has_one and hos_many associations.
cloned_person = person.clone_deep
cloned_person.save
#dup should be used instead of #clone, since attributes are not copied in case of the latter.
module Promotion
class Banner
has_many :localizations
has_many :images
def deep_dup!
duplicate = dup
duplicate.save
duplicate.localizations = localizations.collect { |localization| localization.dup }
duplicate.images = images.collect { |image| image.dup }
duplicate
end
end
end
http://api.rubyonrails.org/classes/ActiveRecord/Core.html#method-i-clone
http://api.rubyonrails.org/classes/ActiveRecord/Core.html#method-i-dup
Well, it's not really a copy.
But you could do
new_model.child_ids = template_model.child_ids

Resources