Chaining multiple .map calls together, more efficient way to query data? - ruby-on-rails

I find myself sometimes needing to "find" an item that's buried deep within an association structure.
In this case, it starts with a task_group. And from that task_group, I need to get the parent_image's, which resides at the end of several associations.
Here is how I'm currently handling it:
task_group.tasks
.map(&:submissions).flatten
.map(&:taggables).flatten
.map(&:images).flatten
.map(&:parent_image)
Is there a more efficient way of going about this type of query?
Perhaps the query could somehow limit the content the amount of data returned each time? For example, rather than have &:submissions, &:taggables, &:images .map calls return the entire records each time, could I have them pull out only, say, the :id attribute, and let the final &:parent_image map call return the full record?
Thank you in advance for any guidance or insights!
UPDATE: Learning about how to use the .joins method as recommended by #Deepak . I'm almost there!
parent_images = Image.joins(crops: [tags: [:submission [task: :task_group]]])
This returns an Image::ActiveRecord_Relation that I can call .ids or .uniq on to get an array.
It seems .joins directly references field names. Which is well and good, except in this instance, taggables that I used in the original .map calls is a quick and dirty "catch-all" for several different associations.
has_many :tags
has_many :people
has_many :businesses
...
def taggables
tags + people + businesses ...
end
I wonder, then, is it possible to reference a method as part of .joins query (possibly with .where(), though I'm not sure how that would it in...)? Or will I need to explicitly state each of the taggables associations directly somehow?

Thanks to #Deepak's suggestion, I am able to combine .joins with .where in the following manner, in order to get all of the parent_images related to a given task_group:
tg = TaskGroup.find(2)
parent_images = Image.joins(crops: [tags: [submission: [task: :task_group]]]).where(task_groups: { id: tg })
Or say I'm to pull all of the images related to a given TaskGroup, one association 'removed' from the parent_images:
tg = TaskGroup.find(2)
images = Image.joins(tags: [submission: [task: :task_group]]).where(task_groups: { id: tg })
Now, my understanding of .joins is that the arguments need to relate to fields found in their respective tables. This means that, when using .map, I could call the taggables method, but when using .joins, this does not seem to be possible.
So the workaround I'm using at the moment is to call each of the taggables models separately, then combine the results.
tag_crops = Image.joins(people: [submission: [task: :task_group]]).where(task_groups: { id: tg })
business_crops = Image.joins(businesses: [submission: [task: :task_group]]).where(task_groups: { id: tg })
...
And then combine each of those together into one giant list
crop_image_ids = tag_crops.ids.uniq +
person_crops.ids.uniq +
business_crops.ids.uniq +
...
crop_image_ids = crop_image_ids.uniq

Related

Rails - finding an object in an active record association by an attribute without issuing more queries

Very short question, I feel like the answer must be already on StackOverflow but I couldn't find it.
I have some incoming parameters. They each have a unique ID.
I query the database and get an active record association using something like:
existing_things = current_user.things.where(uuid: [param_uuids])
This gives me an Active Record Association of those objects.
However, what I later do is something like:
existing_things.where(uuid: some_specific_uuid)
Of course, when I run the above query, it issues an SQL statement.
What I would love to do is find an object in a pre-loaded Active Record array of objects, and return that object without issuing another query.
How should I do that?
The same way you would if it was an ordinary array: Enumerable#find.
existing_things.find { |t| t.uuid == some_specific_uuid }
(or select if you're expecting more than one match)
If you're doing a lot of lookups like that, you might want to get a Hash involved:
things_by_uuid = existing_things.index_by(&:uuid)
things_by_uuid[some_specific_uuid]
(again, if you're expecting more than one match per value, there's group_by)

Rails - Returning all records for who a method returns true

I have a method:
class Role
def currently_active
klass = roleable_type.constantize
actor = Person.find(role_actor_id)
parent = klass.find(roleable_id)
return true if parent.current_membership?
actor.current_membership?
end
end
I would like to return all instances of Role for who this method is true, however can't iterate through them with all.each as this takes around 20 seconds. I'm trying to use where statements, however they rely on an attribute of the model rather than a method:
Role.where(currently_active: true)
This obviously throws an error as there is no attribute called currently_active. How can I perform this query the most efficient way possible, and if possible using Active Records rather than arrays?
Thanks in advance
It seems impossible, in your case you have to do iterations. I think the best solution is to add a Boolean column in your table, so you can filter by query and this will be much faster.
After seeing your method after edit, it seems that it's not slow because of the loop, it is slow because Person.find and klass.find , you are doing alot of queries and database read here. (You better use associations and do some kind of eager loading, it will be much faster)
Another work-around is you can use ActiveModelSerializers , in the serializer you can get the attributes on the object based on condition. and after that you can work your logic to neglect the objects that have some kind of flag or attribute.
See here the documentation of active model serializer
Conditional attributes in Active Model Serializers
Wherever possible you better delegate your methods to SQL through activerecord when you're seeking better efficiency and speed and avoid iterating through objects in ruby to apply the method. I understand this is an old question but still many might get the wrong idea.
There is not enough information on current_membership? methods on associations but here's an example based on some guess-work from me:
roleables = roleable_type.pluralize
roleable_type_sym = roleable_type.to_sym
Role.joins(roleables_sym).where(" ? BETWEEN #{roleables}.membership_start_date AND #{roleables}.membership_end_date", DateTime.current).or(Role.joins(:person).where(" ? BETWEEN persons.membership_start_date AND persons.membership_end_date", DateTime.current))
so you might have to re-implement the method you have written in the model in SQL to improve efficiency and speed.
Try the select method: https://www.rubyguides.com/2019/04/ruby-select-method/
Role.all.select { |r| r.currently_active? }
The above can be shortened to Role.select(&:currently_active?)

find_by_sql which model should I use?

There are three tables:
users
schedules
schedules_users
The user-model and the schedules-model each have the has_and_belongs_to_many-relationship.
Now I simply want to do this:
user_id_binded = Schedule/User/Object/#I dont know!#.find_by_sql ["SELECT schedules_users.user_id FROM schedules_users WHERE schedules_users.schedule_id = ?", schedule.id]
#user_schedules_binded = User.find(user_id_binded)
BUT the return-value of the first find_by_sql must be a model, as I understood the Rails.Api correctly.
It's neither a user-model-return-value or a schedule-model-return-value.
In the schedules_users-table are all relationships between users and schedules.
So I want to get all users which are binded to a specific schedule.
First I thought this should be the right way to solve it, but at that moment I didn't know that the return-value must be a model.
How could I solve this problem?
It appears you have a schedule ID and want the users in the end - that can be done easier by join statement like #user_schedules_binded = User.joins(:schedules).where(schedules: { id: schedule_id })
Or, if you have the schedule object, schedule.users will do the same, both going through schedules_users table.

Rails query object class are different?

#category = Category.where(:category_name => 'cricket')
#category.class
Here the class of #category is "ActiveRecord::Relation"
But,
#category = Category.all(:conditions => { :category_name => 'cricket' })
#category.class
In this case the class of #category is "Array"
The result of both the queries are same, then also the class is different.
WHY?
One more thing...
In the first case, I can do #category.title or #category.body etc.
But in second case, It is not possible.
WHY?
In the first case you are actually using the default scope and attribute it with the where part. It means, when you want to use the items of this Relation, it will run the SQL query on demand. Think about it like it is prepared, but not yet ran query, which will yield walues when needed, and you can further specify the parameters, for example you can append another where clause, or something to it. And of course it is smarter than a simple array, because of the implementetion is more complex behind this.
In the second case you immediately fetch all record from the database, so the result is an Array, containing the results. It is pretty dumb compared to the other one.

How do I search for elements whose collection contains another element in Grails?

Let's say I have a domain class called "User" which can follow other "User" objects. It does so having a field specified as:
def hasMany=[followedUsers:User]
I need to do the reverse (find all User objects that follow a specific User object) without setting up the reverse relationship, since it is not a use case performed often. I tried to do something like this, using closures:
User.findAll { it.followedUsers.contains(userInstance) }
but this always returns all users in the database, regardless of their follow status. I tried doing with HQL but failed miserably as well.
Could anyone give me a quick pointer on the simplest way to accomplish this? Thank you.
You can use this HQL query:
User.executeQuery(
'select u from User u where :follower in elements(u.followedUsers)',
[follower: userInstance])
In my opinion, this syntax is much cleaner:
User.withCriteria { followedUsers{ eq('id', userInstance.id) } }

Resources