Rails - include? with object or object id - arrays

I have an object called Company which has a method called owners that returns an array of User objects. I want to filter those companies by checking if the current user is in the owner's array. first I did something like this (which works):
Company.select { |c| c.owners.include?(current_user) }
However, I figured a more efficient check would be comparing only the ids instead of whole objects:
Company.select { |c| c.owners.map(&:id)include?(current_user.id) }
Can anyone help me understand if there is a difference between the two options?

There's really no difference between the two. Both are very inefficient and won't work if your Company table is large. They load all the Company records into memory, and furthermore call c.owners which fires an additional query on each of the records. This is called an N+1 query. You can address the N+1 part by using Company.all.includes(:owners), but you'd still have the issue of loading everything into memory.
It's hard to give you exact code since you haven't shared your model definition (specifically, how the Company#owners association is defined). But I'll assume you have a CompanysOwners join table. In which case I would recommend the following code:
companies = CompanysOwners.where(owner: current_user).includes(:company).map(&:company)
This only fires off a single query, and doesn't load more records into memory than are needed. You could get rid of the includes(:company) part and it would still work, but be slower.
If your Company#owners association is defined differently than this, feel free to leave a comment and I can show you how to modify this for your needs.
By the way, to address your original question more directly ... under the hood, c.owners.include?(current_user) uses == to compare the records. And ActiveRecord::Core#== is comparing the IDs under the hood. You can see the source code here for proof of that: https://api.rubyonrails.org/v6.1.3.1/classes/ActiveRecord/Core.html#method-i-3D-3D. So they really are doing the same thing.

I have an object called Company which has a method called owners that returns an array of User objects.
Well that's your problem right there.
This should really be handled on the model layer by setting up an indirect association:
class User < ApplicationRecord
has_many :company_ownerships,
foreign_key: :owner_id,
inverse_of: :owner
has_many :companies, through: :company_ownerships
end
class CompanyOwnership < ApplicationRecord
belongs_to :owner, class_name: 'User'
belongs_to :company
end
class Company < ApplicationRecord
has_many :company_ownerships
has_many :owners, through: :company_ownerships
end
This will let you get the companies owned by a user by simply calling:
current_user.companies
This is vastly more efficient as it does a single query to fetch the associated records instead of loading the entire table into memory. This will return an ActiveRecord::Relation object and not an array which allows you to add additional scopes if needed:
current_user.companies.where(bankrupt: false)
Its also superior from a code design standpoint as the buisness logic is encapsulated in your models instead of leaking the implementation details all over the place.
It will also let you include/preload/eager_load to avoid n+1 queries:
#users = User.include(:companies)
.all
#users.each do |user|
# this loads all the companies in one single query instead
# of one query per user
user.companies.each do |company|
puts company.name
end
end
If you ever for some reason need to check if two records are related you want to use a join and where clause:
#companies = Company.joins(:owners)
.where(users: { id: current_user.id })

Related

How to limit amount of associations in Elixir Ecto

I have this app where there is a Games table and a Players table, and they share an n:n association.
This association is mapped in Phoenix through a GamesPlayers schema.
What I'm wondering how to do is actually quite simple: I'd like there to be an adjustable limit of how many players are allowed per game.
If you need more details, carry on reading, but if you already know an answer feel free to skip the rest!
What I've Tried
I've taken a look at adding check constraints, but without much success. Here's what the check constraint would have to look something like:
create constraint("games_players", :limit_players, check: "count(players) <= player_limit")
Problem here is, the check syntax is very much invalid and I don't think there actually is a valid way to achieve this using this call.
I've also looked into adding a trigger to the Postgres database directly in order to enforce this (something very similar to what this answer proposes), but I am very wary of directly fiddling with the DB since I should only be using ecto's interface.
Table Schemas
For the purposes of this question, let's assume this is what the tables look like:
Games
Property
Type
id
integer
player_limit
integer
Players
Property
Type
id
integer
GamesPlayers
Property
Type
game_id
references(Games)
player_id
references(Players)
As I mentioned in my comment, I think the cleanest way to enforce this is via business logic inside the code, not via a database constraint. I would approach this using a database transaction, which Ecto supports via Ecto.Repo.transaction/2. This will prevent any race conditions.
In this case I would do something like the following:
begin the transaction
perform a SELECT query counting the number of players in the given game; if the game is already full, abort the transaction, otherwise, continue
perform an INSERT query to add the player to the game
complete the transaction
In code, this would boil down to something like this (untested):
import Ecto.Query
alias MyApp.Repo
alias MyApp.GamesPlayers
#max_allowed_players 10
def add_player_to_game(player_id, game_id, opts \\ []) do
max_allowed_players = Keyword.get(opts, :max_allowed_players, #max_allowed_players)
case is_game_full?(game_id, max_allowed_players) do
false -> %GamesPlayers{
game_id: game_id,
player_id: player_id
}
|> Repo.insert!()
# Raising an error causes the transaction to fail
true -> raise "Game #{inspect(game_id)} full; cannot add player #{inspect(player_id)}"
end
end
defp is_game_full?(game_id, max_allowed_players) do
current_players = from(r in GamesPlayers,
where: r.game_id == game_id,
select: count(r.id)
)
|> Repo.one()
current_players >= max_allowed_players
end

DB query from a belongs_to relationship

I have a model: Animal
belongs_to :owner
I have a model: Owner
has_many :animals
Owner has a boolean attribute active
def self.not_active
where('owner.active == ?', false)
end
The above code does not work for some reason. I've tried lots of things around it, but keep getting errors. When calling something like Animal.first.owner.active I get back either true or false, but the database call must be different...
For clarification I want the Animals for which their owner is not active. I don't want to do the opposite call though (Starting with the owner db and checking active and returning animals) because of calls I'm linking together off of Animal
Your code should be:
def self.not_active
includes(:owner).where("owners.active = ?", false)
end
When referencing a model in pure SQL, as you are doing so above, you need to use the table name, not the singular model name. Thus, owners, not owner.
A clearer style would be:
def self.not_active
includes(:owner).where(owners: {active: false})
end
Rails AREL will take care of referencing the active attribute in the query correctly, and you don't have to worry about the exact table/model name.

Cost of updating entities in datastore (and, possible to append properties)?

I have a two part question.
Let's say I have a entity with a blob property...
# create entity
Entity(ndb.Model):
blob = ndb.BlobProperty(indexed=False)
e = Entity()
e.blob = 'abcd'
e_key = e.put()
# update entity
e = e_key.get()
e.blob += 'efg'
e.put()
So questions are:
The first time I put() that entity, the cost is 2 Write Ops; how many Ops does it cost to update the entity, as in the above example?
When I added 'efg' to the property, the old property had to be read into memory first, does app engine provide a way to append the old value without reading it first?
There are no partial updates. Every time you overwrite the whole entity. Numbers of indexes will also have an impact on cost. You might like to have a look at https://developers.google.com/appengine/articles/life_of_write for a detailed breakdown of what happens.

Rails 3 Retrieving All Great Grandchild Records As ActiveRecord Collection

So I'm trying to perform queries on a models great granchildren. The relationship is as so...
Tournament > Competitions > Matches > Players
and the Tournament model:
class Tournament < ActiveRecord::Base
has_many :competitions, :dependent => :destroy
has_many :matches, :through => :competitions
has_many :players, :through => :matches
end
Currently what I have for retrieving all the great great grandchildren records is:
#results = #tournament.competitions.collect{|b| b.matches.collect{|c| c.players.with_win_count}}.flatten
And this works, however the problem is that it returns an array. What I'm trying to do is build a dynamic query based on user input, and the player table is essentially a result pool for all matches and the user has the option to filter only what he wants to see. What I have in the end is a fairly potentially complex (depending on user input) query and the additional where clauses cannot be performed on an array. To give you a better idea of how this is meant to work, here is my code...
def results
#tournament = Tournament.find(params[:id])
#results = #tournament.all_great_grandchildren
#results.where(params[:player_condition1]) if params[:player_condition1]
#results.where(params[:player_condition2]) if params[:player_condition2]
#results.where(params[:player_condition3]) if params[:player_condition3]
if params[:match_condition]
#Join to match table so we can query the match fields
#results.join(:match)
#results.where(params[:match_condition])
end
....
#results.order(params[:order]) if params[:order]
end
Is there a way to find all of the great grandchildren (player) records for any given tournament without an array so I can continue to condition the records?
I think you should just call #tournament.players will work out of the box if you have setup with the above associations.
Chamnap has got it. The reason we define “has_many” and “belongs_to” associations in models is to give us easy access to the association models.
So when you have a Tournament object called tournament, you can do
competitions = tournament.competitions
matches = tournament.matches
players = tounaments.players
You can also go in the reverse direction, starting with a player object. Ex: matches = player.matches
With Active Record you shouldn't have to do manual joins or raw SQL model calls when dealing with associations. Have a quick read of the Actice Records Associations guide:
http://guides.rubyonrails.org/association_basics.html

Search entries in Go GAE datastore using partial string as a filter

I have a set of entries in the datastore and I would like to search/retrieve them as user types query. If I have full string it's easy:
q := datastore.NewQuery("Products").Filter("Name =", name).Limit(20)
but I have no idea how to do it with partial string, please help.
q := datastore.NewQuery("Products").Filter("Name >", name).Limit(20)
There is no like operation on app engine but instead you can use '<' and '>'
example:
'moguz' > 'moguzalp'
EDIT: GAH! I just realized that your question is Go-specific. My code below is for Python. Apologies. I'm also familiar with the Go runtime, and I can work on translating to Python to Go later on. However, if the principles described are enough to get you moving in the right direction, let me know and I wont' bother.
Such an operation is not directly supported on the AppEngine datastore, so you'll have to roll your own functionality to meet this need. Here's a quick, off-the-top-of-my-head possible solution:
class StringIndex(db.Model):
matches = db.StringListProperty()
#classmathod
def GetMatchesFor(cls, query):
found_index = cls.get_by_key_name(query[:3])
if found_index is not None:
if query in found_index.matches:
# Since we only query on the first the characters,
# we have to roll through the result set to find all
# of the strings that matach query. We keep the
# list sorted, so this is not hard.
all_matches = []
looking_at = found_index.matches.index(query)
matches_len = len(foundIndex.matches)
while start_at < matches_len and found_index.matches[looking_at].startswith(query):
all_matches.append(found_index.matches[looking_at])
looking_at += 1
return all_matches
return None
#classmethod
def AddMatch(cls, match) {
# We index off of the first 3 characters only
index_key = match[:3]
index = cls.get_or_insert(index_key, list(match))
if match not in index.matches:
# The index entity was not newly created, so
# we will have to add the match and save the entity.
index.matches.append(match).sort()
index.put()
To use this model, you would need to call the AddMatch method every time that you add an entity that would potentially be searched on. In your example, you have a Product model and users will be searching on it's Name. In your Product class, you might have a method AddNewProduct that creates a new entity and puts it into the datastore. You would add to that method StringIndex.AddMatch(new_product_name).
Then, in your request handler that gets called from your AJAXy search box, you would use StringIndex.GetMatchesFor(name) to see all of the stored products that begin with the string in name, and you return those values as JSON or whatever.
What's happening inside the code is that the first three characters of the name are used for the key_name of an entity that contains a list of strings, all of the stored names that begin with those three characters. Using three (as opposed to some other number) is absolutely arbitrary. The correct number for your system is dependent on the amount of data that you are indexing. There is a limit to the number of strings that can be stored in a StringListProperty, but you also want to balance the number of StringIndex entities that are in your datastore. A little bit of math with give you a reasonable number of characters to work with.
If the number of keywords is limited you could consider adding an indexed list property of partial search strings.
Note that you are limited to 5000 indexes per entity, and 1MB for the total entity size.
But you could also wait for Cloud SQL and Full Text Search API to be avaiable for the Go runtime.

Resources