ActiveRecord Scope: Strange behaviour with select on relation - ruby

I have two different models with a 1:N relation.
Let's name them 'myobject' and 'related'
class Myobject < ActiveRecord::Base
has_many :related
scope :without_related, includes(:related).select{ |o| o.related.size == 0 }
end
class Related < ActiveRecord::Base
end
The defined scope seems to work great as long as I don't create new assignments from Myobjects to Related:
Direct rails c command "Myobject.includes(:related).select ... (as defined in Scope) works as expected
Calls to scope "Myobject.without_related" still return objects that have been assigned in the meantime
It seems that this can be fixed by restarting the rails console or restarting Webrick.
But I can't always restart a webapplication only because a relation between objects has been changed ;)
Is there any way to fix this problem or to write the scope in a better way?
PS: I need this query as scope to pass its name as group_method to a grouped_select in the form of the Myobject model

Your problem is that in fact your scope is not scope :)
Scopes must return relations, but your scope returns array.
Though it can work as you expect, if you wrap it in lambda
scope :without_related, lambda{ includes(:related).select{ |o| o.related.size == 0 } }
But I recommend to rewrite this code as usual class method to not mislead those who'll work with this code in future
def self.without_related
includes(:related).select{ |o| o.related.size == 0 }
end
or use counter cache, as advised in other answer.

I would recommend you to use counter_cache for this, you need to add column *related_count* of type int to Myobject, make migration and then you will be able to do so:
class Myobject < ActiveRecord::Base
has_many :related
scope :without_related, where(related_count: 0)
end
class Related < ActiveRecord::Base
belongs_to :myobject, counter_cache: true
end
After that you will have super fast scope for getting all objects with no related records and an a count of that objects as well

Or if you know column name that should be present in related table, use this definition:
class Myobject < ActiveRecord::Base
has_many :related
scope :without_related, includes(:related).where('related.id', true)
end

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

Rails metaprogramming: singleton_class and associations

I'm trying to understand metaprogramming in rails, creating validations and associations dynamically on a class.
Let's say I have the following models:
class House < ActiveRecord::Base
belongs_to :owner
end
class Owner < ActiveRecord::Base
end
Now let's say my House model has a boolean attribute is_ownable, and I only want the house to have the owner association if is_ownable==true.
I thought this would work:
class House < ActiveRecord::Base
after_initialize :create_associations
after_find :create_associations
def create_associations
if self.is_ownable
self.singleton_class.belongs_to :owner
end
end
end
Now when I build or find a record of House, the create_associations function gets called with no errors, but then when I try to access the House.first.owner it throws ActiveRecord::AssociationNotFoundError.
Am I misunderstanding something about how AR associations work?
I hate to say it but this is probably a bad idea. Models should have consistent relationships even if they're not utilized on every model. This is not only against the spirit of ActiveRecord or Ruby, but object oriented programming in general. In most cases objects of a particular class are expected to have an identical interface for the sake of consistency and clarity. Adding methods to individual objects is permitted, but there should be exceptional circumstances to justify such a thing.
That's not to say you can't get the effect you want in a more idiomatic way:
class House < ActiveRecord::Base
belongs_to :owner
validates :validate_owner_assignment
protected
def validate_owner_assignment
if (self.ownable? and !self.owner)
self.errors.add(:owner, "is required if ownable")
elsif (!self.ownable? and self.owner)
self.errors.add(:owner, "cannot be assigned if not ownable")
end
end
end
Now assigning owner will trigger a save failure of type ActiveRecord::RecordInvalid if the expectations aren't met.
I'd advocate calling your booleans x and not is_x to reduce verbosity. The vast majority of the time the is_ part is redundant.

Is it possible to get Rails associations to return subclasses instead of the superclass?

First, the inheritance approach may not be correct. If so, please explain a different approach.
Here is an example of the setup.
I have many applications, other than using the exact same database, that are not connected. To dry up the code base, I have an engine with all of the active record classes. I would like to keep application specific scopes and methods within the main applications.
In my Rails engine,
class MyEngine::User < ActiveRecord::Base
has_many :dogs
end
class MyEngine::Dog < ActiveRecord::Base
belongs_to :user
end
In my main app,
class User < MyEngine::User
end
class Dog < MyEngine::Dog
# EDITED TO SHOW EXAMPLE OF HOW SCOPE DOESN'T BELONG IN ENGINE
DOGS_WITH_SPOTS_IDS = [ 1, 2, 3, 4 ]
scope :with_spots, -> { where(id: DOGS_WITH_SPOTS_IDS) }
end
The issue in my main app,
user = User.last
user.dogs
# => #<ActiveRecord::Associations::CollectionProxy[#<MyEngine::Dog spots: true>]>
user.dogs.with_spots
NoMethodError: undefined method 'with_spots' for #<MyEngine::Dog::ActiveRecord_Association_CollectionProxy:0x007fa4bf838e50>
Although this works
class User < MyEngine::User
has_many :dogs
end
I would prefer not to redefine/define all of the associations in the main apps.
This type of problem is just going to keep occurring in different forms throughout all of my main applications. This made me think that there may be a way to redefine the associations.
Is there a way to evaluate the association on the subclass instead of the superclass much like the STI pattern to get Rails helpers to recognize different subclasses?
Meaning, I would like my main app User#dogs to return main app Dog instead of MyEngine::Dog.
# may not be the exact code, just off the top of my head
instance_eval do
def model_name
self.class.name
end
end
Important Note You can't query user.dogs.with_spots, even without inheritance. Your probably mean to get array of Dogs in user.dogs.to_a.
Solution The most general way to solve your problem is to use Single Table Inheritance.
Let's see how it works.
I will assume there are already tables my_engine_users and my_engine_dogs defined with all the required columns (as much as MyEngine module is concerned).
Classes MyEngine::User and MyEngine::Dog remain unchanged.
Classes User and Dog just extend respective engine-classes without any additional declarations about relations among them.
What we do, we tell Rails, that the two tables might be used by different classes (via extension). It's easily achievable by adding type column to the tables.
add_column :my_engine_users, :type, :string
and
add_column :my_engine_dogs, :type, :string
Now you can do all the wonderful things with your users and dogs.
dimakura = User.create(username: 'dimakura')
dora = Dog.create(name: 'Dora', owner: dimakura)
fanta = Dog.create(name: 'Fanta', owner: dimakura)
dimakura.dogs.to_a # => array of Dogs, not MyEngine::Dogs
This magic is achieved by explicitly writing class name in type column.
p dimakura #=> #<User id: 1, username: "dimakura", type: "User">
p dora #=> #<Dog id: 1, owner_id: 1, name: "Dora", type: "Dog">
p fanta #=> #<Dog id: 2, owner_id: 1, name: "Fanta", type: "Dog">
MyEngine::User.dogs returns [proxied by an association instance] a set of MyEngine::Dog instances, that in general might have no with_spots scope.
If you are aware that all MyEngine::Dogs “respond” to with_spots, just move the scope definition to MyEngine::Dog. If that is not the case, calling this scope on MyEngine::User.dogs makes no sense.
UPD the example added might be solved:
class MyEngine::Dog < ActiveRecord::Base
belongs_to :user
scope :with_spots, -> { self.class.const_get(:SCOPE_PROC) }
end
class Dog < MyEngine::Dog
DOGS_WITH_SPOTS_IDS = [ 1, 2, 3, 4 ]
SCOPE_PROC = -> { where(name: DOGS_WITH_SPOTS_IDS }
end
I just wanted to post a follow up:
While #dimakura is correct, it still did not work for what I was trying to do. This is because Rails does not allow chaining scopes for STI. In the end, I removed the STI and used the following:
user = User.first
user.dogs.merge(Dog.with_spots)

Where is Person and not Person::Single

Lets say I have three Classes
Person
Person::Single < Person
Person::Married < Person
Lets say I have two Persons that are Single => Person::Single
Then i get two Persons when I do:
Person.all.count
=> 2
But my goal is to get only the persons that are type Person and not Person::Single or Person::Married
What I tried is:
Person.where(type: Person.to_s)
=> 2
but this also returns 2 because Person::Single and Person::Married inherit from Person
What can I do instead?
Is there for example an way to say that ::Single and ::Married have the same methods from person but are not the same type? Thanks
Since this is marked with the rails tag, I assume your Person class is an ActiveRecord model. There is some more information missing, but I will just assume where I don't really know ;)
Your Person class inherits the ActiveRecord methods, for example "all", and Person::Single (and ::Maried) will inherit it through Person.
So, naturally, when you do a Person.all it will just do the regular <#ActiveRecord>.all method, which does not know and not care about ::Single and ::Maried.
What you can do is dynamically setting the default_scope, based on the class name of which you are calling .all, or .where or whatever on.
class Person < ActiveRecord::Base
...
def self.default_scope
scope = if self.name == Person.sti_name
where(:type => Person.sti_name)
else
nil
end
scope
end
end
This will also scope your where(:whatever => x) queries.
However, this is asuming you have an attribute called "type" for you Person model (column in database).
EDIT: As Holger pointed out, there is already magic involved with rails STI. I still think it is viable to set a default scope, only you should skip setting the scopes for single and married.
If you don't need it as a default scope, you should just try Person.where(:type => Person.sti_name) or Person.where(:type => Person.name) since rails asserts the class name to sti_name if the class inherits from ActiveRecord::Base.
Try this:
Person.where(:type => Person).count

Resources