Mongoid, how to order_by through a references_one association (and subsequent associations)? - ruby-on-rails-3

Simple model:
class hat
embedded_in :owner
field :color
end
class owner
embedds_one :hat
referenced_in :house
field :name
end
class house
references_one :owner
field :number
end
So simply puts, we have collection of houses that are associated to an owner, and the owner can have a colored hat.
I can simply sort the house by their number:
House.all.order_by( [[ :number, :asc ]])
But what I want is ordering the house, by the name of their owner, ideally I would like to write:
House.all.order_by( [[ :'owner.name', :asc ]])
But it does not work...
And even further I would like to be able to sort the houses by the color of the owner's hat
House.all.order_by( [[ :'owner.hat.color', :asc ]])
Any idea how I can achieve this without rewriting everything if possible :)
Thanks
ALex

Dot notation is possible only for embedded documents, but you have 2 collections - houses and owners.
In one of the Owner records in MongoDB you have only field house_id, in one of the House records you dont have any connection to Owner model.
So the only way is to fetch all Houses and then to sort achieved collection using Enumerable#sort.

ways to do this would be:
- embed the owner name as string in the house doc.
The house object would have both a owner_id (so that you can fetch full owner doc) and an extra field with just the owner name as string.
You only need to change the name when changing the owner_id and that can be done in 1 update so it is consistent.
Using this it will be a very efficient query since it doesnt need to look across collections and docs for reads.
Downside is a bit more mem used.
have the owner object reference the house, since the owner "owns".
Then you can query owner sorted by name, then fetch the reference house documents that you will get in the correct order.
This has the downside of doing many individual queries.
one extreme solution is to embed all those docs: hat inside owner, owner inside house.
best,
AG

Related

How do I sort an active record collection by a value held by one of its associations?

I have an Active Record Model called Owner. It is associated with both a House and a CoOwner. An Owner also has a column called debt_amount. In my case, every Owner will always be attached to both a House and a CoOwner
A house has a location field
I am looking to run a query to find all Owners attached to a specific co-owner, with nil for debt_amount. This is working fine.
Where I am running into trouble is when I attempt to sort this the associated Houses.location.
Below is what I am currently using.
Owner.includes(:house).where(co_owner_id: 10)
.where.not(debt_amount: nil)
.order([house.location])
Any ideas on how to make this work, or pointers to resources I can use to do some reading?
Owner.includes(:house)
.where(co_owner_id: 10)
.where.not(debt_amount: nil)
.order("houses.location ASC")
Here are more examples, https://apidock.com/rails/ActiveRecord/QueryMethods/order

Django: Annotate table with field across a M2M mapping to another table

I want to get competition.name from a list of submissions.
In my setup, competitions and teams share a M2M relationship (with an associated competition-team object. Each competition-team pair can submit any number of submissions. I now have a dashboard page which I am trying to create a table of all submissions by the team accompanied by the respective competition's name. The output should look like:
| Submission Name | Submission Date etc. | Competition Name |
| Sub01 | 2020-12-30 2000 | Competition01 |
I have trouble retrieving the competition name from the submissions. Here are my models:
class Competition(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=30)
class CompetitionTeam(models.Model):
competition_id = models.ForeignKey('Competition', on_delete=models.CASCADE, to_field='id', db_column='competition_id')
team_id = models.ForeignKey('Team', on_delete=models.CASCADE, to_field='id', null=True, db_column='team_id')
class CompetitionSubmission(models.Model):
competitionteam_id = models.ForeignKey(CompetitionTeam, on_delete=models.CASCADE, db_column='competitionteam_id')
I wish to annotate a set of submissions with their respective competition names. I tried with:
submissions.annotate(competition_name=Subquery(Competition.objects.filter(id=Subquery(CompetitionTeam.objects.get(id=OuterRef('competitionteam_id')).competition_id)).values('name')))
"ValueError: This queryset contains a reference to an outer query and may only be used in a subquery."
I also tested with the following command:
CompetitionSubmission.objects.prefetch_related('competitionteam_id__competition_id')
It runs but the command seems to do nothing. I will update this post with other methods I try.
Thank you.
EDIT
submissions.annotate(competition_name=Subquery(Competition.objects.filter(id=Subquery(CompetitionTeam.objects.filter(id=OuterRef(OuterRef('competitionteam_id_id'))).values('competition_id'))).values('name')))
Seems to work correctly.
You can traverse ForeignKeys directly using double underscores.
CompetitionSubmission.objects.values(
'competitionteam_id',
'competitionteam_id__competition_id',
'competitionteam_id__competition_id__name',
'competitionteam_id__team_id',
'competitionteam_id__team_id__name',
)
This will only produce a single database query. Django ORM takes care of everything.
P.S. I would avoid using '_id' in field names as Django model fields are supposed to be referring to related objects themselves. Django automatically adds extra attributes with '_id' that contains the related object's id. Please see https://docs.djangoproject.com/en/3.1/ref/models/fields/#database-representation

Fastest way to return a list of records depending on many many-to-many relationships

I'm developing an API in Rails in which exists users and messages tables. Also users have a gender (gender_id), belong to a country (country_id) and also have a civil status (civil_status_id) and the messages are created by admins.
So up to here I have this model.
Now I have to develop the following requirement
An admin should be able to create a message targeted to users depending on its attributes (country, gender or civil status). Also, the admin should be able to declare a message as a global message, in this case "all" users should receive it, but exceptions should also be allowed. For example, in the case where an admin want to send a message to users from all countries, except people from Russia and China.
The thing is I'm no Rails/SQL expert, but I want to make this efficiently so that if tomorrow the app has ten thousand or a hundred thousand users the server responds quickly.
So I was thinking the following
First create 3 many-to-many relationships (countries_messages, genders_messages and civil_statuses_messages). The record of these tables represent the relations between the messages and the countries, civil_statuses and genders.
Then create a form where an admin can create a message, where by means of several select boxes, he should be able to choose the attributes of the users to whom he wants to reach. The form for creating a message should also have a checkbox to determine if the message is global, if its marked then I would consider that the selected countries, genders and civil statuses would be the categories that the administrator wants to exclude, i.e. if an admin want to send a message to all the people in the system except for people who are from Canada he should mark the global option and select the country Canada in the select box (obviously this would be stated in the view).
Now up to here I have this model.
In what I do have doubts is which way is more efficient to return the messages that corresponds to a user.
Method 1
When an admin specifies that a message is global, except for those from country with id 3 then I could add to countries_messages records like (country_id: 1, message_id: 2), (country_id: 2, message_id: 2), (country_id: 4, message_id: 2), ..., etc. i.e. forming a relation with every country except the country with id 2.
Then retriveng the messages that the current user should read like the following:
global_messages = Message.where(global: true).ids
country_messages = current_user.country.messages.ids
gender_messages = current_user.gender.messages.ids
civil_status_messages = current_user.current_status.messages.ids
#messages = Message.find(global_messages + country_messages + gender_messages + civil_status_messages)
Method 2
Other way could be forming a relation of that message with the excluded country, i.e. if I make a message exclusively for people from country with id 2 then I should add the record (country_id: 2, message_id: 2) in countries_messages, but in the contrary case if I made a message to every country except the country with id 2 then I should also add the record (country_id: 2, message_id: 2) to countries_messages.
In this case I can know if a message is excluded for males and people from Argentina, for example, if the message is global AND it's associated with the country and gender record that represents Argentina and males.
Then the retriveng of the messages that the current user should read would be like this:
global_messages = Message.where(global: true).ids
country_messages = current_user.country.messages
gender_messages = current_user.gender.messages
civil_status_messages = current_user.current_status.messages
excluded_country_messages_ids = country_messages.where(global: true).ids
excluded_gender_messages_ids = gender_messages.where(global: true).ids
excluded_civil_status_messages_ids = civil_status_messages.where(global: true).ids
#messages = Message.find(global_messages + country_messages + gender_messages + civil_status_messages - excluded_country_messages_ids - excluded_gender_messages_ids - excluded_civil_status_messages_ids)
There could be more ways to do the same, so I want to receive recommendations or if you see that I could make improvements to do the same then tell me. If there is something you do not understand ask.
Depending on your database choice, you may want to consider storing the message "attributes" (country, gender, ...) in a jsonb column on your messages table. It could also include a user_ids attribute to simplify things. The queries might look a little funny, but you can move a lot into scopes to clear things up in your code and even add indexes to speed things up.
Here is a great article on using jsonb with indexes in Rails with postgresql.
Another alternative you could do is make a UserMessage join table with a polymorphic reference that could associate the different attributes to a message and user:
class UserMessage < ApplicationRecord
belongs_to :user
belongs_to :message
belongs_to :messageable_type # e.g. "Country"
belongs_to :messageable_id # e.g. some Country id
end

How to find_or_initialize based on two fields when both fields correspond to possible uninitialized objects

Hi guys I have a situation where on a form I'm taking in orders for a car servicing application. I have the following models:
Car
belongs_to :car_company
Car_company
has_many :cars
Services
attributes_accessible :car_company_id, :car_id
#virtual attributes
attributes_accessible :car_company_name, :car_reg
The thing is that on a single form the user can enter in the name of the car company as well as the registration number of a car. If the company name doesnt exist it creates a new company and associates it with the service and the same goes for the car. I got this part working however the thing is that I want that on submitting this form the car created should be automatically associated with the car_company whether the carcompany exists or doesn't exist.
I'm pretty stuck here on how to get this thing done the right way? Its basically just to avoid having to enter the car details and the company details seperately just to use them on a form. Any ideas guys?
I see you are using an unconventional model name. By convention in rails, your model should be CarCompany. However, I think what you have will work.
Putting something like this in the appropriate controller may be something like what you want. If not, please clarify what you want.
car_company = Car_company.find_or_initialize_by_name(params[:car_company_name])
car = Car.find_or_initialize_by_registration(params[:car_registration])
car_company.cars << car
car_company.save
You actually may be able to combine the middle two lines with car_company.cars.find_or..., but I'm not sure if that works or not.
I hope that helps.

Use non-persisted fields in composed_of class

I've got credit card info in a Booking object that I can't store in the database because of PCI compliance. So a few of the fields I can save (customer's name), but a few I can't (card number, etc).
My question: how can I get the data that isn't persisted into my composed_of class (CreditCard, in this case).
I've tried adding:
attr_accessor :card_number
composed_of :credit_card, :mapping => [%w(card_number card_number), %w(first_name, first_name)]
to Booking. The first_name flows through because it's a database column, but card_number does not.
Any suggestions on how to get this into the system correctly? I can just go straight to the card I guess, but I feel like that's bound to result in some kind of hidden problems, since I'm:
adding mutability to a value object
circumventing whatever is going on behind the scenes
Any suggestions?