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.
Related
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 })
I'm trying to figure out how I'm going to 'CRUD' the order of items I have in a group that I'm storing in a database. (Pseudo statement of: select * items from app where group_id = 1;)
My guess is that I just use an numeric field and just increase/decrease the number as more items are added to/removed from the group. I can then just update the items number in this field as they are moved around. However, I've seen this go really badly wrong in an old legacy app where items would get out of sync and you'd have a group where the order ended up something like this:
0,1,1,3,4,5
0,1,1,1,4,5
This wasn't handled very gracefully by the application either, and broke the application necessitating manual intervention to reorder the items in the DB.
Is there a way to avoid this pitfall?
EDIT: I would also maybe want the items available in multiple groups with multiple orders.
I think in that case I would need a many to many relationship for both the group to item relationship and the item to order relationship. /EDIT
I'll be doing this in the Django framework.
I'm not really sure what you are asking; because ordering is one thing, and grouping of related objects is something else entirely.
Databases don't store the order of things, but rather the relationships (grouping) of things. The order of things is a user interface detail and not something that a database should be used for.
In django, you can create a ManyToMany relationship. This essentially creates a "box" where you can add and remove items that are related to a particular model. Here is the example from the documentation:
from django.db import models
class Publication(models.Model):
title = models.CharField(max_length=30)
# On Python 3: def __str__(self):
def __unicode__(self):
return self.title
class Meta:
ordering = ('title',)
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
# On Python 3: def __str__(self):
def __unicode__(self):
return self.headline
class Meta:
ordering = ('headline',)
Here an Article can belong to many Publications, and Publications have one or more Articles associated with them:
a = Article.create(headline='Hello')
b = Article.create(headline='World')
p = Publication.create(title='My Publication')
p.article_set.add(a)
p.article_set.add(b)
p.save()
# You can also add an article to a publication from the article object:
c = Article.create(headline='The Answer is 42')
c.publications.add(p)
To know how many articles belong to a publication:
Publication.objects.get(title='My Publication').article_set.count()
An NDB model contains two properties: email and password. How to avoid adding to the database two records with the same email? NDB doesn't have UNIQUE option for a property, like relational databases do.
Checking that new email is not in the database before adding—won't satisfy me, because two parallel processes can both simultaneously do the checking and each add the same email.
I'm not sure that transactions can help here, I am under this impression after reading some of the manuals. Maybe the synchronous transactions? Does it mean one at a time?
Create the key of the entity by email, then use get_or_insert to check if exists.
Also read about keys , entities. and models
#ADD
key_a = ndb.Key(Person, email);
person = Person(key=key_a)
person.put()
#Insert unique
a = Person.get_or_insert(email)
or if you want to just check
#ADD
key_a = ndb.Key(Person, email);
person = Person(key=key_a)
person.put()
#Check if it's added
new_key_a =ndb.Key(Person, email);
a = new_key_a.get()
if a is not None:
return
Take care. Changing email will be really difficult (need to create new entry and copy all entries to new parent).
For that thing maybe you need to store the email, in another entity and have the User be the parent of that.
Another way is to use Transactions and check the email property. Transaction's work in the way: First that commits is the First that wins. A concept which means that if 2 users check for email only the first (lucky) one will succeed, thus your data will be consistent.
Maybe you are looking for the webapp2-authentication module, that can handle this for you. It can be imported like this import webapp2_extras.appengine.auth.models. Look here for a complete example.
I also ran into this problem, and the solution above didn't solve my problem:
making it a key was unacceptable in my case (i need the property to be changeable in the future)
using transactions on the email property doesn't work AFAIK (you can't do queries on non-key names inside transactions, so you can't check whether the e-mail already exists).
I ended up creating a separate model with no properties, and the unique property (email address) as the key name. In the main model, I store a reference to the email model (instead of storing the email as a string). Then, I can make 'change_email' a transaction that checks for uniqueness by looking up the email by key.
This is something that I've come across as well and I settled on a variation of #Remko's solution. My main issue with checking for an existing entity with the given email is a potential race condition like op stated. I added a separate model that uses an email address as the key and has a property that holds a token. By using get_or_insert, the returned entities token can be checked against the token passed in and if they match then the model was inserted.
import os
from google.appengine.ext import ndb
class UniqueEmail(ndb.Model):
token = ndb.StringProperty()
class User(ndb.Model):
email = ndb.KeyProperty(kind=UniqueEmail, required=True)
password = ndb.StringProperty(required=True)
def create_user(email, password):
token = os.urandom(24)
unique_email = UniqueEmail.get_or_insert(email,
token=token)
if token == unique_email.token:
# If the tokens match, that means a UniqueEmail entity
# was inserted by this process.
# Code to create User goes here.
# The tokens do not match, therefore the UniqueEmail entity
# was retrieved, so the email is already in use.
raise ValueError('That user already exists.')
I implemented a generic structure to control unique properties. This solution can be used for several kinds and properties. Besides, this solution is transparent for other developers, they use NDB methods put and delete as usual.
1) Kind UniqueCategory: a list of unique properties in order to group information. Example:
‘User.nickname’
2) Kind Unique: it contains the values of each unique property. The key is the own property value which you want to control of. I save the urlsafe of the main entity instead of the key or key.id() because is more practical and it doesn’t have problem with parent and it can be used for different kinds. Example:
parent: User.nickname
key: AVILLA
reference_urlsafe: ahdkZXZ-c3RhcnQtb3BlcmF0aW9uLWRldnINCxIEVXNlciIDMTIzDA (User key)
3) Kind User: for instance, I want to control unique values for email and nickname. I created a list called ‘uniqueness’ with the unique properties. I overwritten method put in transactional mode and I wrote the hook _post_delete_hook when one entity is deleted.
4) Exception ENotUniqueException: custom exception class raised when some value is duplicated.
5) Procedure check_uniqueness: check whether a value is duplicated.
6) Procedure delete_uniqueness: delete unique values when the main entity is deleted.
Any tips or improvement are welcome.
class UniqueCategory(ndb.Model):
# Key = [kind name].[property name]
class Unique(ndb.Model):
# Parent = UniqueCategory
# Key = property value
reference_urlsafe = ndb.StringProperty(required=True)
class ENotUniqueException(Exception):
def __init__(self, property_name):
super(ENotUniqueException, self).__init__('Property value {0} is duplicated'.format(property_name))
self. property_name = property_name
class User(ndb.Model):
# Key = Firebase UUID or automatically generated
firstName = ndb.StringProperty(required=True)
surname = ndb.StringProperty(required=True)
nickname = ndb.StringProperty(required=True)
email = ndb.StringProperty(required=True)
#ndb.transactional(xg=True)
def put(self):
result = super(User, self).put()
check_uniqueness (self)
return result
#classmethod
def _post_delete_hook(cls, key, future):
delete_uniqueness(key)
uniqueness = [nickname, email]
def check_uniqueness(entity):
def get_or_insert_unique_category(qualified_name):
unique_category_key = ndb.Key(UniqueCategory, qualified_name)
unique_category = unique_category_key.get()
if not unique_category:
unique_category = UniqueCategory(id=qualified_name)
unique_category.put()
return unique_category_key
def del_old_value(key, attribute_name, unique_category_key):
old_entity = key.get()
if old_entity:
old_value = getattr(old_entity, attribute_name)
if old_value != new_value:
unique_key = ndb.Key(Unique, old_value, parent=unique_category_key)
unique_key.delete()
# Main flow
for unique_attribute in entity.uniqueness:
attribute_name = unique_attribute._name
qualified_name = type(entity).__name__ + '.' + attribute_name
new_value = getattr(entity, attribute_name)
unique_category_key = get_or_insert_unique_category(qualified_name)
del_old_value(entity.key, attribute_name, unique_category_key)
unique = ndb.Key(Unique, new_value, parent=unique_category_key).get()
if unique is not None and unique.reference_urlsafe != entity.key.urlsafe():
raise ENotUniqueException(attribute_name)
else:
unique = Unique(parent=unique_category_key,
id=new_value,
reference_urlsafe=entity.key.urlsafe())
unique.put()
def delete_uniqueness(key):
list_of_keys = Unique.query(Unique.reference_urlsafe == key.urlsafe()).fetch(keys_only=True)
if list_of_keys:
ndb.delete_multi(list_of_keys)
I am trying to solve the racing problem based on this to prevent duplicate user registrations. So if the account exists or the email has been used, no entity will be created.
#ndb.transactional
def get_or_insert2(account, email):
accountExists, emailExists = False, False
entity = Member.get_by_id(account)
if entity is not None:
accountExists = True
if Member.query(Member.email==email).fetch(1):
emailExists = True
if not accountExists and not emailExists:
entity = Member(id=account)
entity.put()
return (entity, accountExists, emailExists)
My questions:
I got an error message: BadRequestError: Only ancestor queries are allowed inside transactions. what was the problem?
Is the code correct? I mean, can it really solve the racing problem?
Thanks.
Transactions work on entity groups, and you can include up to 5 entity groups in a cross group transaction. An entity group is handled by a single server (or group, replicated), which means it is able to have consistent internal state when checking data or doing ancestor queries within the entity group.
Regular queries are global, on indexes with eventual consistency. You don't know when all changes from all nodes have been included in an index. You can't lock up the entire datastore to get consistent snapshot state for your transaction. This is a key difference from a regular RDBMS if you're used to consistent index for queries.
For 1), the problem is that you're doing a regular query inside a transaction, which doesn't work as explained above. The answer to 2) then becomes no, query can't solve racing problem, you need explicit gets.
You will need a Model for Member, Email and SSN. This is a quick untested example that hopefully gets you going:
class Member(ndb.Model):
email = ndb.KeyProperty()
ssn = ndb.KeyProperty()
# More user properties goes here...
class Email(ndb.Model):
member = ndb.KeyProperty()
class SSN(ndb.Model):
member = ndb.KeyProperty()
#ndb.tasklet
def get_or_insert2(account, email, ssn):
created = False
member_key = ndb.Key(Member, account)
email_key = ndb.Key(Email, email)
ssn_key = ndb.Key(SSN, ssn)
member_obj, email_obj, ssn_obj = yield ndb.get_multi_async([member_key, email_key, ssn_key])
if member_obj is None and email_obj is None and ssn_obj is None:
member_obj = Member(key=member_key, email=email_key, ssn=ssn_key))
email_obj = Email(key=email_key, member=member_key)
ssn_obj = SSN(key=ssn_key, member=member_key)
yield ndb.put_multi_async([member_obj, email_obj])
created = True
raise ndb.Return([created, member_obj, email_obj, ssn_obj])
outcome = ndb.transaction(lambda: get_or_insert2(account, email, ssn), xg=True)
I'm not sure if it works to combine #ndb.tasklet and #ndb.transactional(xg=True) decorators, and if so, which order, just try it out.
If you need to query User based on email or ssn, you could for example rename the KeyProperties to *_ref and make something like
#ndb.ComputedProperty
def email(self):
return self.email_ref.id()
While this ends up being more lines of code than you anticipated, it is conceptually simple and straight forward, and you can easily figure out what's going on when you get back to it later.
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