How can I create a Polymorphic Ruby Method? - ruby

I have a class -- AccountGroup -- which has a polymorphic relation to various Account classes (i.e. AwordsAccount, BingAccount, etc...). I've defined a helper method -- accounts -- that aggregates all of the different account types:
def accounts
adwords_accounts + bing_ads_accounts + facebook_accounts + linkedin_accounts
end
Now, I'd like to extend this method so that I can use it to add accounts as well as list them:
account_group.accounts << an_adwords_account
which should call:
account_group.adwords_accounts << an_adwords_account
under the hood. How do I differentiate between calling the method with the modifier << vs. calling it without the modifier?
Thanks!

Here's how I would implement this. The Account model uses single table inheritance and has a type column that distinguishes between the different account types:
class Account < ActiveRecord::Base
belongs_to :account_group
end
class AdwordsAccount < Account
end
class BingadsAccount < Account
end
class FacebookAccount < Account
end
class LinkedinAccount < Account
end
In your AccountGroup model you can then create associations to all of these without any problems:
class AccountGroup < ActiveRecord::Base
has_many :accounts
has_many :adwords_accounts
has_many :bingads_accounts
has_many :facebook_accounts
has_many :linkedin_accounts
end
Now everything works as expected and accounts contains all of the other types combined. You might need to call reload on the other associations when you add/remove accounts, but i'm not sure about that. Just try it out.

Related

Convenience of CollectionProxy for has_one?

I have an Accounts model, which have many CreditCards and has one BillingInfo.
In CreditCards controller I initialize with the help of CollectionProxy:
class CreditCardsController < ApplicationController
def create
credit_card = current_account.credit_cards.new(credit_card_params)
...
end
end
However, this doesn't work with has_one association:
class BillingInfosController < ApplicationController
def create
billing_info = current_account.billing_info.new(billing_info_params)
...
end
end
The reason is; calling billing_info on current_account does return nil rather than empty CollectionProxy, which results sending new on nil and exists with NoMethodError.
Is there a way to use CollectionProxy or something similar to keep using
current_account.billing_info.new(billing_info_params)
rather than something like
BillingInfo.new(billing_info_params.merge(account_id: current_account.id))
to initialize? Thanks in advance!
You should be able to use current_account.build_billing_info or current_account.create_billing_info which are methods added by the has_one association.
When initializing a new has_one or belongs_to association you must use the build_ prefix to build the association, rather than the association.build method that would be used for has_many or has_and_belongs_to_many associations. To create one, use the create_ prefix.
See the has_one association reference for more about these methods and the other methods active record adds for you.
one solution is ensure every account has_one billing_info
you can user after_create callback to create a account's billing_info
another one is get billing_info first
billing_info = current_account.billing_info || current_account.build_billing_info
billing_info.assign_attributes(billing_info_params)

Rails overriding active record setter in a relation

I want to override the << setter in my relation. For example, given:
class Library < ActiveRecord::Base
has_many :groups
def readers
groups.find_by(name: 'readers').users
end
end
class Group < ActiveRecord::Base
has_many :group_memberships
has_many :users, through: :group_memberships
end
class GroupMembership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class User < ActiveRecord::Base
has_many :groups, through :group_membership
end
I want to do something like
someLibrary.readers << user1
and some additional things to happen after this.
The code should look something like:
def <<(objects)
super objects
#do other things here
end
Where should it be? I guess in Group, like:
class Group
...
def users<<(objects)
super objects
#do stuff
end
end
but I only want to do it when I'm invoking << on readers.
I want to know if there is a way to know whether I'm invoking << on a group users relationship, or whether I have access to group object when I'm invoking << method on group users through the relationship.
I want to do it because it looks nice. The easiest way would be to define separate method to set readers (and be more explicit), but I want to know if it is possible in activerecord or in ruby.
edit:
Yeah I know that overriding core methods is bad thing and people go to hell for that, yada yada yada.
I'm just curious how it's done. Like, for learning purposes.
Besides the aim is just to override the << method on that particular relation so probable there might be some justification why someone might want to do it.
Obligatory disclaimer:
I do not recommend that you do this, in 'important' code. Changing the behaviour of methods like this will confuse the hell out of other developers (as well as your future self), and lead to all sorts of unintended behavioural changes!
But assuming that this is 'just for fun'...
Based on the information above, someLibrary.readers returns a collection of User records. So all we need to do is add the desired behaviour to that class.
Normally you can do this by just defining a class method, in one of two ways:
class User
def self.foo
puts 'this works!'
end
class << self
def bar
puts 'this works too!'
end
end
end
With the above in place, you can call the methods like:
someLibrary.readers.foo
someLibrary.readers.bar
...However, there is some rails black magic going on under the hood here. someLibrary.readers is actually an instance of User::ActiveRecord_Associations_CollectionProxy, and the above methods are being picked up dynamically and appended to ActiveRecord::Associations::CollectionProxy.
Because of this dynamic method definition, it is not possible to override existing Rails methods (such as <<) in this manner. Instead, we'll need to monkey-patch the User::ActiveRecord_Associations_CollectionProxy class directly:
class User
class ActiveRecord_Associations_CollectionProxy
def <<(objects)
super(objects)
# do stuff
end
end
end
If you're looking for a better way of doing this however, I'd recommend using a service object design pattern. You can then encapsulate any more complex/custom logic relating to creating/updating/deleting users, libraries, etc. in a clean and isolated abstraction.
The more established way to do this...
class Library < ActiveRecord::Base
has_many :groups
has_one :reader_group -> {groups.find_by(name: 'readers')}
has_many :readers, through: :reader_group, class_name: 'User', foreign_key: 'user_id'
end
And that's it. You can now do
my_library.readers << another_user

Issues with Polymorphic/STI in Ruby

We're having a problem with polymorphism & STI in Ruby
Our database has two tables: 'account' and 'list'. 'list' has columns 'account_id', 'type', 'description'.
Our classes look like so:
class Account < ActiveRecord::Base
has_many :lists
has_many :subscription_lists
has_many :email_lists
end
class List < ActiveRecord::Base
belongs_to :account
end
class SubscriptionList < List
end
class EmailList < List
end
Inside Account, methods email_lists and subscription_lists work exactly as expected. Our goal is we want to be able to call lists which will return an array of all lists. Currently, that doesn't work, nor does self.lists
Oddly, Account.find(self.id).lists DOES give us an array of all the lists associated.
What gives? How do we fix this?
You can use List.all which will return an ActiveRecord::Relation of all List objects, regardless of type.
Additionally, you can use #instance_variable.lists on any instance variable of Account
What's more, you could use a query on a class method to accomplish the job, like so, which will also return an ActiveRecord::Relation:
List.where('account_id = ?', id)
Lastly, your Account association with List should not include the children of List:
class Account < ActiveRecord::Base
has_many :lists, inverse_of: :account, dependent: :destroy
end

Rails 3.1 - Binding HABTM from other controller

I have:
class Person < ActiveRecord::Base
has_many :people_phones
has_many :phones, :through => :people_phones
end
I also have:
class Request < ActiveRecord::Base
belongs_to :person
belongs_to :phone
end
Now when someone call with a request I open "requests#new" form, fill in person_id and phone_number and other details and submits them to "requests#create" controller#action.
In the "requests#create", I can do:
#phone = Phone.find_or_create_by_phone_number(params[:phone][:phone_number])
But how can I bind Person with that Phone from this Requests controller?
I mean create a record in people_phones table (if it doesn't exists)?
User.find(person_id).phones << #phone
I don't really know how your app works, but you see the idea.
If you have a request, and you want to "validate" it, you would do
request.person.phones << request.phone
Interesting stuff to know, it's kind of related (I'll try to find where I found that, it was a long time ago)
Steps required for the object to be saved to database:
New
Blog.new(…).save
user.blogs << Blog.new(…)
user.blogs.new(…).save – do not use, no practical use case
Build
Blog.build – not possible
user.blogs.build(…), user.save – both are required to save to DB
Create
Blog.create(…)
user.blogs.create(…)

Ruby on Rails 3: How can I sort ActiveRecords by an attribute of another table?

I need to query a database table and get the rows ordered by a count of an association. Is there a Rails (like Active Record Query) way to do this?
My models and their associations are as follows:
class User < ActiveRecord::Base
has_one :business
end
class Business < ActiveRecord::Base
has_many :postulations
end
class Postulation < ActiveRecord::Base
belongs_to :business
end
I need to get a number of Users ordered by the amount of Postulations that their Business has. Is there a clean way to do this or do I just have to query with find_by_sql?
Thank you.
User.includes(:business => :postulations).group("users.id").order("count(postulations.id) desc").limit(20)
This will probably work

Resources