Suppose I have a mapped class Article. It has a relation category that does a query each time I access it (article.category would issue a query to get the category or article).
How do I proxy the article.category call so that the result is queried from the database, then remembered and then returned?
Thanks, Boda Cydo.
Does SA really issue a query every time you access the relation in the same session? IMO, it should not happen, as the result should get cached automatically within the session.
In order to check that no SQL is issued, just turn on logging and see for yourself:
metadata.bind.echo = 'debug'
c = session.query(Child).first() # issues SELECT on your child table(s)
print "child: ", c
print "c.parent: ", c.parent # issues SELECT on your parent table, then prints
print "c.parent: ", c.parent # just prints (no SQL)
print "c.parent: ", c.parent # just prints (no SQL)
Shall your code work otherwise by default, please provide code snippet.
In case you really just need to cache the result, see below (very similar solution to another question you posted):
class MyChild(Base):
__tablename__ = 'MyChild'
id = Column(Integer, primary_key=True)
parent = relation('Parent')
# ... other mapped properties
def __init__(self):
_parent_cached = None
#property
def parent_cached(self):
if self._parent_cached is None:
self._parent_cached = self.parent
But in order to have the result when your object is detached from the session you must call this property before detaching. (Also it does not handle situation when the parent is None. Do you always have parent?).
The option with eager load is simplier and once you load the object, you should have the relation loaded already (key is to have lazy=False):
class MyChild(Base):
__tablename__ = 'MyChild'
id = Column(Integer, primary_key=True)
parent = relation('Parent', lazy=False)
# ... other mapped properties
...
Related
I have a question regarding the execution order in a Rails method : Here what I do in my model :
def update_fields
FillMethods.fill_info("BadgeUser", "Badge", self.id, self.badge_id, "badge_")
self.reload
self.update_attributes(:completed => true) if self.badge_goal == 0
end
I have an object, then I apply the FillMethods info on it to fill some fields in this object and save it. Then I want to check if badge_goal == 0 to mark it as completed.
The issue I have is that if I don't put self.reload the self item will not be updated, the object will be in the database but the self item will be the same as before the FillMethods. If I do self.reload the self item is correct and it can be marked as completed correctly
My question is will ruby wait for the FIllMethods to be done to reload the self or it will reload the self before the FillMethods is done ?
Why does the self item is not correct if I don't do self.reload ?
The FillMethods is a module in my lib directory.
Thanks for your help,
Thats my fill method :
module FillMethods
def self.fill_info(model1, model2, id1, id2, string)
parameters1 = model1.constantize.attr_accessible[:default].to_a.map {|s| s.dup}
parameters1.delete_if {|s| !s.start_with?(string)}
parameters2 = parameters1.map {|s| s = s.split(string)[1]}
h = Hash[parameters1.zip parameters2]
object1 = model1.constantize.find(id1)
object2 = model2.constantize.find(id2)
h.each do |parameter1, parameter2|
object1.update_attribute(parameter1.to_sym , object2.send(parameter2))
end
return object1
end
end
The goal of the method is to fill the BadgeUser table with all the badge info.
For each column in my Badge table (like name) I have in my BadgeUser table badge_name
Thanks,
I cannot be sure without seeing the code, but judging from the parameters you pass, I guess that FillMethods.fill_info retrieves the record from db again, using the third parameter id. It then changes the record and stores it back.
Your object (self) has no way, under ActiveRecord or similar, to know that the db was modified somewhere somehow.
Note that you are retrieving the same record from db some three times instead than once.
If you change FillMethods.fill_info to instead accept a record (self itself), and modify it, then self would be in the new state.
Addendum
Ruby code is executed sequentially in a single thread unless you explicitly start a new thread, so yes, .fill_info is executed before continuing with the rest of update_fields.
So I have 2 tables actor and actor2role. The latter is a lookup (junction) table to relate actor, role and dvd. I need to create a query with aliases, so I have this method:
def self.remove_duplicate_by_id(id)
offendingActor = self.find(id).actor # get the actor's name
ids = self.find_by_sql("SELECT MIN(id) AS minId, MAX(id) AS maxId, actor FROM `dvd_actor` WHERE actor = '#{offendingActor}'")
rolesForOffender = ids.actor2role # throws error
end
The problem is that ids is not an ActiveRecord object so I can't use the actor2role method (which is a relationship I've established between the 2 tables in Rails and works when you do something like Actor.first.actor2role.
so the questions is: Am I doomed to do this manually and then issue another sql query to recreate what the actor2role method would accomplish or is there some way to do this with Rails objects?
I'd really like to do it all natively if possible because I also have to issue these queries:
UPDATE dvd_actor2role SET actorid = $d->minId WHERE actorId = $d->maxId");
DELETE FROM dvd_actor2role WHERE actorId = $d->maxId LIMIT 1");
Is this even possible?
In the end I went with this which seems to do the trick. If anyone can spot any code that could be optimized, or something inherently wrong (and feels like chiming in) please feel free to comment.
actorObject = self.find_by_id(id) # get the object because we need it below for other queries
offendingActor = actorObject.actor
ids = self.select("MIN(id) AS minId, MAX(id) AS maxId, id, actor").find_by_actor(offendingActor)
rolesForOffender = actorObject.actor2role
rolesForOffender.each do |r|
# first find out if the relationship already exists or we get a SQL error for the foreign key relationship.
exists = Actor2role.where("actorId = ? AND roleId = ?", ids.minId, r.roleId)
if exists.nil?
Actor2role.update_all("actorId = #{ids.minId}, actorId = #{ids.maxId}")
end
end
self.destroy(ids.maxId) # delete this guy in actor table
end
I want to create a view using tastypie to expose certain objects of the same type, but with the following two three twists:
I need to get the objects using three separate queries;
I need to add a field which doesn't exist in the underlying model, and the value of that field depends on which of the queries it came from; and
The data will be per-user (so I need to hook in to one of the methods that gets a request).
I'm not clear on how to hook into the tastypie lifecycle to accomplish this. The recommended way for adding a "virtual" field is in the dehydrate method, which only knows about the bundle it's operating on.
Even worse, there's no official way to join querysets.
My problem would go away if I could get tastypie to accept something other than a queryset. In that case I could pass it a list of subclasses of my object, with the additional field added.
I'm open to any other sensible solution.
Edit: Added twist 3 - per-user data.
In the last version you should override the dehydrate method, e.g.
def dehydrate(self, bundle):
bundle.data['full_name'] = bundle.obj.get_full_name()
return bundle
Stumbled over similar problem here. In my case, items in the list could be "checked" by user.
When an item is retrieved by AJAX, its checked status is returned with the resource as a normal field.
When an item is saved to the server, "checked" field from the resource is stored in user's session.
First I thought hydrate() and dehydrate() methods to be the best match for this job, but turned out there are problems with accessing request object in these. So I went with alter_data_to_serialize() and obj_update(). I think there's no need to override obj_create(), since item can't be checked when it's first created, I think.
Here is the code, but note that it hasn't been properly tested yet.
class ItemResource(ModelResource):
def get_object_checked_status(self, obj, request):
if hasattr(request, 'session'):
session = request.session
session_data = session.get(get_item_session_key(obj), dict())
return session_data.get('checked', False)
return False
def save_object_checked_status(self, obj, data, request):
if hasattr(request, 'session'):
session_key = get_item_session_key(obj)
session_data = request.session.get(session_key, dict())
session_data['checked'] = data.pop('checked', False)
request.session[session_key] = session_data
# Overridden methods
def alter_detail_data_to_serialize(self, request, bundle):
# object > resource
bundle.data['checked'] = \
self.get_object_checked_status(bundle.obj, request)
return bundle
def alter_list_data_to_serialize(self, request, to_be_serialized):
# objects > resource
for bundle in to_be_serialized['objects']:
bundle.data['checked'] = \
self.get_object_checked_status(bundle.obj, request)
return to_be_serialized
def obj_update(self, bundle, request=None, **kwargs):
# resource > object
save_object_checked_status(bundle.obj, bundle.data, request)
return super(ItemResource, self)\
.obj_update(bundle, request, **kwargs)
def get_item_session_key(obj): return 'item-%s' % obj.id
OK, so this is my solution. Code is below.
Points to note:
The work is basically all done in obj_get_list. That's where I run my queries, having access to the request.
I can return a list from obj_get_list.
I would probably have to override all of the other obj_* methods corresponding to the other operations (like obj_get, obj_create, etc) if I wanted them to be available.
Because I don't have a queryset in Meta, I need to provide an object_class to tell tastypie's introspection what fields to offer.
To expose my "virtual" attribute (which I create in obj_get_list), I need to add a field declaration for it.
I've commented out the filters and authorisation limits because I don't need them right now. I'd need to implement them myself if I needed them.
Code:
from tastypie.resources import ModelResource
from tastypie import fields
from models import *
import logging
logger = logging.getLogger(__name__)
class CompanyResource(ModelResource):
role = fields.CharField(attribute='role')
class Meta:
allowed_methods = ['get']
resource_name = 'companies'
object_class = CompanyUK
# should probably have some sort of authentication here quite soon
#filters does nothing. If it matters, hook them up
def obj_get_list(self, request=None, **kwargs):
# filters = {}
# if hasattr(request, 'GET'):
# # Grab a mutable copy.
# filters = request.GET.copy()
# # Update with the provided kwargs.
# filters.update(kwargs)
# applicable_filters = self.build_filters(filters=filters)
try:
#base_object_list = self.get_object_list(request).filter(**applicable_filters)
def add_role(role):
def add_role_company(link):
company = link.company
company.role = role
return company
return add_role_company
director_of = map(add_role('director'), DirectorsIndividual.objects.filter(individual__user=request.user))
member_of = map(add_role('member'), MembersIndividual.objects.filter(individual__user=request.user))
manager_of = map(add_role('manager'), CompanyManager.objects.filter(user=request.user))
base_object_list = director_of + member_of + manager_of
return base_object_list #self.apply_authorization_limits(request, base_object_list)
except ValueError, e:
raise BadRequest("Invalid resource lookup data provided (mismatched type).")
You can do something like this (not tested):
def alter_list_data_to_serialize(self, request, data):
for index, row in enumerate(data['objects']):
foo = Foo.objects.filter(baz=row.data['foo']).values()
bar = Bar.objects.all().values()
data['objects'][index].data['virtual_field'] = bar
return data
I have been looking at the sqlalchemy recipes on their wiki, but don't know which one is best to implement what I am trying to do.
Every row on in my tables have an user_id associated with it. Right now, for every query, I queried by the id of the user that's currently logged in, then query by the criteria I am interested in. My concern is that the developers might forget to add this filter to the query (a huge security risk). Therefore, I would like to set a global filter based on the current user's admin rights to filter what the logged in user could see.
Appreciate your help. Thanks.
Below is simplified redefined query constructor to filter all model queries (including relations). You can pass it to as query_cls parameter to sessionmaker. User ID parameter don't need to be global as far as session is constructed when it's already available.
class HackedQuery(Query):
def get(self, ident):
# Use default implementation when there is no condition
if not self._criterion:
return Query.get(self, ident)
# Copied from Query implementation with some changes.
if hasattr(ident, '__composite_values__'):
ident = ident.__composite_values__()
mapper = self._only_mapper_zero(
"get() can only be used against a single mapped class.")
key = mapper.identity_key_from_primary_key(ident)
if ident is None:
if key is not None:
ident = key[1]
else:
from sqlalchemy import util
ident = util.to_list(ident)
if ident is not None:
columns = list(mapper.primary_key)
if len(columns)!=len(ident):
raise TypeError("Number of values doen't match number "
'of columns in primary key')
params = {}
for column, value in zip(columns, ident):
params[column.key] = value
return self.filter_by(**params).first()
def QueryPublic(entities, session=None):
# It's not directly related to the problem, but is useful too.
query = HackedQuery(entities, session).with_polymorphic('*')
# Version for several entities needs thorough testing, so we
# don't use it yet.
assert len(entities)==1, entities
cls = _class_to_mapper(entities[0]).class_
public_condition = getattr(cls, 'public_condition', None)
if public_condition is not None:
query = query.filter(public_condition)
return query
It works for single model queries only, and there is a lot of work to make it suitable for other cases. I'd like to see an elaborated version since it's MUST HAVE functionality for most web applications. It uses fixed condition stored in each model class, so you have to modify it to your needs.
Here is a very naive implementation that assumes there is the attribute/property self.current_user logged in user has stored.
class YourBaseRequestHandler(object):
#property
def current_user(self):
"""The current user logged in."""
pass
def query(self, session, entities):
"""Use this method instead of :method:`Session.query()
<sqlalchemy.orm.session.Session.query>`.
"""
return session.query(entities).filter_by(user_id=self.current_user.id)
I wrote an SQLAlchemy extension that I think does what you are describing: https://github.com/mwhite/multialchemy
It does this by proxying changes to the Query._from_obj and QueryContext._froms properties, which is where the tables to select from ultimately get set.
How can I interact with objects I've created based on their given attributes in Ruby?
To give some context, I'm parsing a text file that might have several hundred entries like the following:
ASIN: B00137RNIQ
-------------------------Status Info-------------------------
Upload created: 2010-04-09 09:33:45
Upload state: Imported
Upload state id: 3
I can parse the above with regular expressions and use the data to create new objects in a "Product" class:
class Product
attr_reader :asin, :creation_date, :upload_state, :upload_state_id
def initialize(asin, creation_date, upload_state, upload_state_id)
#asin = asin
#creation_date = creation_date
#upload_state = upload_state
#upload_state_id = upload_state_id
end
end
After parsing, the raw text from above will be stored in an object that look like this:
[#<Product:0x00000101006ef8 #asin="B00137RNIQ", #creation_date="2010-04-09 09:33:45 ", #upload_state="Imported ", #upload_state_id="3">]
How can I then interact with the newly created class objects? For example, how might I pull all the creation dates for objects with an upload_state_id of 3? I get the feeling I'm going to have to write class methods, but I'm a bit stuck on where to start.
You would need to store the Product objects in a collection. I'll use an array
product_collection = []
# keep adding parse products into the collection as many as they are
product_collection << parsed_product_obj
#next select the subset where upload_state_ud = 3
state_3_products = product_collection.select{|product| product.upload_state_id == 3}
attr reader is a declarative way of defining properties/attributes on your product class. So you can access each value as obj.attribute like I have done for upload_state_id above.
select selects the elements in the target collection, which meet a specific criteria. Each element is assigned to product, and if the criteria evaluates to true is placed in the output collection.