Rails Joins through a module - ruby-on-rails

I'm trying to do a pretty simple join in my model to list all 'Locations' in a 'Post' with a certain id.
Currently, each post has_many :locations, :through => :location_post. I'm using the 'blogit' gem, which puts posts in a module named 'Blogit::Posts'.
I'm getting a wrong argument type Class (expected Module) error when I try to run the following in my Post.rb model:
module Blogit
class Post < ActiveRecord::Base
before_save :reuse_existing_locations
def reuse_existing_locations
existing_locations = Location.include(Blogit::Post).first
end
How can I do a join through a module?
Thanks in advance!

I'm not sure I understand what you're trying to accomplish so just some notes and observations:
By looking at the code, it's clear that Blogit::Post is a class, not a module.
The include method takes modules (not classes), that's the error you're seeing.
You are calling the include method on the Location model and that seems kind
of strange to me. Did you mean to call includes? But then again that
wouldn't make much sense since it seems like you've got a many to many
relationship between Location and Blogit::Post.
In the Location model (which doesn't need to be in the Blogit namespace), you can simply reference the Blogit::Post model as
follows:
has_many :posts, class_name: "Blogit::Post", ...
If existing_locations is in fact an attribute on the model and you want to assign to it, you need to put self in front of it (as in self.existing_locations). Otherwise you're just creating a local variable.

You probably wanted to use ActiveModels includes instead of Rubys include, which is to include methods from another module.

Related

How to know if an "includes" call was made while working with a single record?

Motivation
The motivation was that I want to embed the serialization of any model that have been included in a Relation chain. What I've done works at the relation level but if I get one record, the serialization can't take advantage of what I've done.
What I've achieved so far
Basically what I'm doing is using the method includes_values of the class ActiveRecord::Relation, which simply tells me what things have been included so far. I.e
> Appointment.includes(:patient).includes(:slot).includes_values
=> [:patient, :slot]
To take advantage of this, I'm overwriting the as_json method at the ActiveRecord::Relation level, with this initializer:
# config/initializers/active_record_patches.rb
module ActiveRecord
class Relation
def as_json(**options)
super(options.merge(include: includes_values)) # I could precondition this behaviour with a config
end
end
end
What it does is to add for me the option include in the as_json method of the relation.
So, the old chain:
Appointment.includes(:patient).includes(:slot).as_json(include: [:patient, :slot])
can be wrote now without the last include:
Appointment.includes(:patient).includes(:slot).as_json
obtaining the same results (the Patient and Slot models are embedded in the generated hash).
THE PROBLEM
The problem is that because the method includes_values is of the class ActiveRecord::Relation, I can't use it at the record level to know if a call to includes have been done.
So currently, when I get a record from such queries, and call as_json on it, I don't get the embedded models.
And the actual problem is to answer:
how to know the included models in the query chain that retrieved the
current record, given that it happened?
If I could answer this question, then I could overwrite the as_json method in my own Models with:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
extend Associations
def as_json(**options)
super(options.merge(include: included_models_in_the_query_that_retrieved_me_as_a_record))
end
end
One Idea
One Idea I have is to overwrite the includes somewhere (could be in my initializer overwriting directly the ActiveRecord::Relation class, or my ApplicationRecord class). But once I'm there, I don't find an easy way to "stamp" arbitrary information in the Records produced by the relation.
This solution feels quite clumsy and there might be better options out there.
class ApplicationRecord < ActiveRecord::Base
def as_json(**options)
loaded_associations = _reflections.each_value
.select { |reflection| association(reflection.name).loaded? }
.map(&:name)
super(options.merge(include: loaded_associations))
end
end
Note that this only loads 1st level associations. If Appointment.includes(patient: :person) then only :patient will be returned since :person is nested. If you plan on making the thing recursive beware of circular loaded associations.
Worth pointing out is that you currently merge include: ... over the provided options. Giving a user no choice to use other include options. I recommend using reverse_merge instead. Or swap the placements around {includes: ...}.merge(options).

Calling class methods from mixed in module in ruby

The scenario: I have a couple of ActiveRecord models in my rails system that all need to be controlled via an access control list. I have a nice little ACL implementation that does what I want, but right now the check-access calls are all duplicated in each controlled object type (document, user, etc).
My intuition is to pull that shared code into a module and use it with a mixin. I'm not sure this is possible (or what the right syntax is), because the mixed-in module has calls to ActiveRecord::Base methods - there's scope and has_many definitions.
The example of what I'd like to accomplish is here:
class Document < ActiveRecord::Base
include Controlled
end
module Controlled
has_many :acls, as: :controlled
scope :accessible, ->(uid, level){where("BUNCH OF SQL HERE")}
def access_convenience_methods
#stuff to provide easy access to authorization checks
end
end
And then I'd have a few other models that derive from ActiveRecord::Base that include Controlled.
It's the has_many and scope calls in the module that are causing heartache - I can't call them from within the mixed-in module, apparently this context doesn't have access to the outer class methods.
Any advice is welcome.
You are correct in that you can't just call class methods from the module like that.
Nowadays the boilerplate code required to do this has been wrapped into ActiveSupport::Concern; it does exactly what you want.
[EDIT]: I also suggest you should study the boilerplate code itself, as it's pretty short and readable and a good example of Ruby metaprogramming.
Aha, this is clearly a ruby newbie failure here - I need to put the has_many and other one-off calls inside an included block. It seems like ActiveSupport::Concern is precisely the right thing to use here:
module Controlled
extend ActiveSupport::Concern
included do
has_many :acls, as: :controlled
scope :accessible, ->(uid, level){where("BUNCH OF SQL HERE")}
end
def access_convenience_methods
#stuff to provide easy access to authorization checks
end
end

Referring to instance in has_many (Rails)

I have a Game model which has_many :texts. The problem is that I have to order the texts differently depending on which game they belong to (yes, ugly, but it's legacy data). I created a Text.in_game_order_query(game) method, which returns the appropriate ordering.
My favourite solution would have been to place a default scope in the Text model, but that would require knowing which game they're part of. I also don't want to create separate classes for the texts for each game - there are many games, with more coming up, and all the newer ones will use the same ordering. So I had another idea: ordering texts in the has_many, when I do know which game they're part of:
has_many :texts, :order => Text.in_game_order_query(self)
However, self is the class here, so that doesn't work.
Is there really no other solution except calling #game.texts.in_game_order(#game) every single time??
I had a very similar problem recently and I was convinced that it wasn't possible in Rails but that I learned something very interesting.
You can declare a parameter for a scope and then not pass it in and it will pass in the parent object by default!
So, you can just do:
class Game < ActiveRecord
has_many :texts, -> (game) { Text.in_game_order_query(game) }
Believe or not, you don't have to pass in the game. Rails will do it magically for you. You can simply do:
game.texts
There is one caveat, though. This will not work presently in Rails if you have preloading enabled. If you do, you may get this warning:
DEPRECATION WARNING: The association scope 'texts' is instance dependent (the scope block takes an argument). Preloading happens before the individual instances are created. This means that there is no instance being passed to the association scope. This will most likely result in broken or incorrect behavior. Joining, Preloading and eager loading of these associations is deprecated and will be removed in the future.
Following up using PradeepKumar's idea, I found the following solution to work
Assuming a class Block which has an attribute block_type, and a container class (say Page), you could have something like this:
class Page
...
has_many :blocks do
def ordered_by_type
# self is the array of blocks
self.sort_by(&:block_type)
end
end
...
end
Then when you call
page.blocks.ordered_by_type
you get what you want - defined by a Proc.
Obviously, the Proc could be much more complex and is not working in the SQL call but after there result set has been compiled.
UPDATE:
I re-read this post and my answer after a bunch of time, and I wonder if you could do something as simple as another method which you basically suggested yourself in the post.
What if you added a method to Game called ordered_texts
def ordered_texts
texts.in_game_order(self)
end
Does that solve the issue? Or does this method need to be chainable with other Game relation methods?
Would an Association extension be a possibility?
It seems that you could make this work:
module Legacy
def legacy_game_order
order(proxy_association.owner.custom_texts_order)
end
end
class Game << ActiveRecord::Base
includes Legacy
has_many :texts, :extend => Legacy
def custom_texts_order
# your custom query logic goes here
end
end
That way, given a game instance, you should be able to access instance's custom query without having to pass in self:
g = Game.find(123)
g.texts.legacy_game_order
Here is a way where you can do it,
has_many :texts, :order => lambda { Text.in_game_order_query(self) }
This is another way which I usually wont recommend(but will work),
has_many :texts do
def game_order(game)
find(:all, :order => Text.in_game_order_query(game))
end
end
and you can call them by,
game.texts.game_order(game)
Im not sure what your order/query looks like in the in_game_order_query class method but i believe you can do this
has_many :texts, :finder_sql => proc{Text.in_game_order_query(self)}
Just letting you know that I have never used this before but I would appreciate it if you let me know if this works for you or not.
Check out http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many for more documentation on :finder_sql
I think if you want runtime information processed you should get this done with:
has_many :texts, :order => proc{ {Text.in_game_order_query(self)} }

How do you create a has_many association with multiple types?

I have the following:
a Link model
a LinkItem model, which I want to be of the following type
a comment
a tag
...
I am using this code:
Link model
class Link < ActiveRecord::Base
has_many :link_items
end
LinkItem model
class LinkItem < ActiveRecord::Base
belongs_to :link
end
class Comment < LinkItem
end
class Tag < LinkItem
end
Now I don't know how to tell Rails that my LinkItem model is supposed to be polymorphic. I've read the Rails Guide on asociations and other tutorials, but these just describe how to create a belongs_to association to multiple other models, not the other way around.
So my question would be:
How do I create a has_many association where the associated instances can be of different types? Or would it be better to create seperate models for comments, tags, etc. and just associate each of them individually with my Link model?
EDIT
Actually my code works.
I just tried using a 'type'-column (instead of 'link_item_type') in my database and rails automatically used it to save/determine the correct subclass of my LinkItems (thanks Wizard of Ogz for the hint)
However I still can't access the subclasses of LinkItem without referencing a LinkItem first. Is this some kind of lazyloading?
If you are looking for polymorphic association nicholaides has the right way .
If you are looking for has_meny polymorphic association , check out the answer to "Setting up a polymorphic has_many :through relationship".
This is called a polymorphic association. Here is some documentation.
I just dealt with what I think is the same issue.
My filename for my model was wrong. I initially created it with one name (ex. link_tag.rb), and then changed the name of the class (ex. from LinkTag to Tag) on the fly without changing the name of the file (ex. tag.rb).
When I renamed the file correctly, it worked as expected.
In summary, the name of the file needed to match the name of the class.
I know this post is a little old, but maybe that will help someone someday!
I user polymorphic associations a lot!
I would first watch this RailsCast and then the documentation suggested by nicholaides.
It perfectly explains how to create both sides of the association.

ActiveRecord model inheritence and polymorphism

I have a class heirarchy but not single-table inheritence.
ThrowAway < ActiveRecord::Base
and
Junk < ThrowAway
the problem is that all ThrowAway objects have references to a Location. A Location
belongs_to :throw_away, :polymprohic => true
The problem is that if I define
ThrowAway < ActiveRecord::Base
has_many :locations, :as => :throw_away
end
then even if Junk inherits from it and defines a different table name, the throw_away_type column will always be set to ThrowAway where I actually want it set to Junk.
Now there will be many of these subclasses so there will be Stuff < ThrowAway Rags < ThrowAway etc. I want them all to have a Location without defining a location relationship in each individual class.
Is this possible with rails? Problem is that there is not just location, there are other of these sort of relationships and I'd rather follow some DRY here. I'm assuming I need to create a generator method which will execute on the current object and generate these relationships generators on runtime.
Thanks.
ActiveRecord doesn't seem to be able to cooperate with model inheritance that's not STI. Fortunately, that's probably not what you really want anyway. You probably want a mixin instead.
Create a module that contains all of the functionality that you want your models to share in common, and have all of your models include that module. I would probably put the module in your models directory, but give it an adjective as a name. Some folks might put the module in the lib directory or create a lib directory within the models directory for it.
In your case, you'll want the association to be defined at the time the module is mixed in, so you'll need to use a callback. Something like..
module Trashable
def self.included(base)
base.send :has_many, :locations, :as => :throw_away
end
end

Resources