DataMapper use only certain columns - ruby

I have a code section like the following:
users = User.all(:fname => "Paul")
This of course results in getting all users called "Paul". Now I only need some of the columns available for each user which leads to replacing the above line by something like this:
users = User.all(:name => "Paul", :fields => [:id, :fname, :lname, :email])
Until now everything works as expected. Unfortunately now I want to work with users but as soon as I use something like users.to_json, also the other columns available will be lazy-loaded even due the fact, that I don't need those. What's the correct or at least a good way to end up with users only containing the attributes for each user that I need?
An intermediate object like suggested in How to stop DataMapper from double query when limiting columns/fields? is not a very good option as I have a lot of places where would need to define at least twice which fields I need and also I would loose the speed improvement gained by loading only the needed data from the DB. In addition such an intermediate object also seems to be quite ugly to build when having multiple rows of the DB selected (=> multiple objects in a collection) instead of just one.

If you usually works with the collection using json I suggest overriding the as_json method in your model:
def as_json(options = nil)
# this example ignores the user's options
super({:only => [:fname]}.merge(options || {}))
end
You are able to find more detailed explanation here http://robots.thoughtbot.com/better-serialization-less-as-json

Related

If I eager load associated child records, then that means future WHERE retrievals won't dig through database again?

Just trying to understand... if at the start of some method I eager load a record and its associated children like this:
#object = Object.include(:children).where(email:"test#example.com").first
Then does that mean that if later I have to look through that object's children this will not generate more database queries?
I.e.,
#found_child = #object.children.where(type_of_child:"this type").first
Unfortunately not - using ActiveRecord::Relation methods such as where will query the database again.
You could however filter the data without any further queries, using the standard Array / Enumerable methods:
#object.children.detect {|child| child.type_of_child == "this type"}
It will generate another database query in your case.
Eager loading is used to avoid N+1 queries. This is done by loading all associated objects. But this doesn't work when you want to filter that list with where later on, Rails will than build a new query and run that one.
That said: In your example the include makes your code actually slower, because it loads associated object, but cannot use them.
I would change your example to:
#object = Object.find_by(email: "test#example.com")
#found_child = #object.children.find_by(type_of_child: "this type")

Active Record class

I am working on a migration project. Wanna migrate a rails 2.x app to 3.x. I have a problem with active record.
In Rails 2.x:
arr=StorageUnit.find(:all, :conditions =>"type='Drawer'")
The above code will get me all records with type Drawer.
arr.class
=> Array
In Rails 3.x:
Here the above function is deprecated. So i had to use
arr=StorageUnit.where("type='Drawer'")
The above code will get me all records with type Drawer.
arr.class
ActiveRecord::Relation
I guess this is because of the change in Active Record.
My problem is i have some code based on this class.
For ex:
if arr.class== Array
do something
else
do something
end
So as off now i have changed it to
if arr.class== ActiveRecord::Relation
do something
else
do something
end
Just curious to know whether there is any better solution or any alternative way to solve it. I have a lot of place where they have used such stuff.
EDIT:
arr=StorageUnit.where("type='Drawer'").all
will provide the class as Array. My objective is to know when the code without suffix can provide you the required records than what is the use of all in the end.? Is it just to change class? Can anyone ecxplain?
StorageUnit.where simply returns the ActiveRecord relation. Tacking on .all will execute the sql and create instances of StorageUnit.
arr = StorageUnit.where(:type => 'Drawer').all
There are many interesting side effects of it being returned as a relation. Amongst other things, you can combine scopes before executing:
StorageUnit.where(:type => 'Drawer').where(:color => 'black')
you can view the resultant sql for debugging:
StorageUnit.where(:type => 'Drawer').to_sql
Imagine this:
class StorageUnit < ActiveRecord::Base
scope :with_drawer, where(:type => 'Drawer')
scope :with_color, lambda { |c| where(:color => c) }
end
Now:
StorageUnit.with_drawer.with_color('black').first_or_create # return the first storage unit with a black drawer
StorageUnit.with_drawer.with_color('black').all # return all storage units with black drawers
The relation allows for underlying query to be built up even saved for later use. all and other modifiers like it have special meaning to the relation and trigger the database execution and building of model instances.

Twitter Ruby Gem - get friends names and ids, nothing more

Is it possible to simply get the people you are following with just an id and full name? I do not need any of the additional data, it's a waste of bandwidth.
Currently the only solution I have is:
twitter_client = Twitter::Client.new
friend_ids = twitter_client.friend_ids['ids']
friends = twitter_client.users(friend_ids).map { |f| {:twitter_id => f.id, :name => f.name} }
is there anyway to just have users returned be an array of ids and full names? better way of doing it than the way depicted above? preferably a way to not filter on the client side.
The users method uses the users/lookup API call. As you can see on the page, the only param available is include_entities. The only other method which helps you find users has the same limitation. So you cannot download only the needed attributes.
The only other thing I'd like to say is that you could directly use the friends variable, I don't see any benefit of running the map on it.

What is the 'right' data structure to turn app features on/off based on 'type' of account?

My app has 10 features that are enabled/disabled depending upon which of the 3 'types' of account a user has.
Currently, I have 10 methods (one per feature) along the lines of:
def is_FEATURENAME_enabled
case currentuser.accounttype
when "A", "C" # account types allow to see that feature
return true
else
return false
end
end
Then, in each place where I potentially disable a feature, I do
if foo.is_SOMEFEATURE_enable
do stuff to enable that feature
end
It works. It's not that hard to maintain. But there should be a better way. I suspect the right solution is to define some sort of structure (hash? I dunno) in one place that maps enabled features to accounttypes, then have a single method that I call something like:
if foo.is_feature_enabled(:FEATURENAME)
do stuff to enable feature
end
where the method is_feature_enabled looks at currentuser.accountype and checks the mapping structure to see if the identified feature is enabled.
And I suspect the DRY way to define that mapping (given I have WAY more features than account types) is to list all the features ONCE then for each feature list the accounttypes that have access to that feature (not the other way around). That way when I add a new feature I only have to edit ONE line in the mapping. Something like:
FeatureA: usertype1
FeatureB: usertype1, usertype3
FeatureC: usertype2
...
seems more logical and easier to maintain than:
usertype1: FeatureA, FeatureB, FeatureD, FeatureG
usertype2: FeatureC, FeatureD
usertype3: FeatureB, FeatureD, FeatureG, FeatureH
Any suggestions would be appreciated, and instructive for learning The Right Way to do stuff in ruby.
I think you've pretty much discovered the best way to do it on your own-- what you suggest is wise. Just use the feature name as a lookup key for your hash, then take the resulting list and check whether that list contains the account type of the current user.
E.g.,
# For example...
$AllowedUserCastes = {
:CanLogin => ["admin", "paiduser", "crazyuser", "anonymous"],
:CanDrink => ["admin", "21yearolduser", "crazyuser"],
:CanArrest => ["admin", "police"]
}
def featureAllowed?( whichFeature )
$AllowedUserCastes[whichFeature].include? currentUserCaste()
end
It sounds like you're looking for some kind of event dispatcher. I've yet to bump into a very good one in ruby. But I'm sure I've missed a few, so I'll be happy to be stood corrected in the comments.

Some problems with MapperExtension of sqlalchemy

There are two classes: User and Question
A user may have many questions, and it also contains a question_count
to record the the count of questions belong to him.
So, when I add a new question, I want update the question_count of the
user. At first, I do as:
question = Question(title='aaa', content='bbb')
Session.add(question)
Session.flush()
user = question.user
### user is not None
user.question_count += 1
Session.commit()
Everything goes well.
But I wan't to use event callback to do the same thing. As following:
from sqlalchemy.orm.interfaces import MapperExtension
class Callback(MapperExtension):
def after_insert(self, mapper, connection, instance):
user = instance.user
### user is None !!!
user.question_count += 1
class Question(Base):
__tablename__ = "questions"
__mapper_args__ = {'extension':Callback()}
....
Note in the "after_insert" method:
instance.user # -> Get None!!!
Why?
If I change that line to:
Session.query(User).filter_by(id=instance.user_id).one()
I can get the user successfully, But: the user can't be updated!
Look I have modified the user:
user.question_count += 1
But there is no 'update' sql printed in the console, and the
question_count are not updated.
I try to add Session.flush() or Session.commit() in the
after_insert() method, but both cause errors.
Is there any important thing I'm missing? Please help me, thank you
The author of sqlalchemy gave me an useful answer in a forum, I copy it here:
Additionally, a key concept of the
unit of work pattern is that it
organizes a full list of all
INSERT,UPDATE, and DELETE statements
which will be emitted, as well as the
order in which they are emitted,
before anything happens. When the
before_insert() and after_insert()
event hooks are called, this structure
has been determined, and cannot be
changed in any way. The
documentation for before_insert() and
before_update() mentions that the
flush plan cannot be affected at this
point - only individual attributes on
the object at hand, and those which
have not been inserted or updated yet,
can be affected here. Any scheme
which would like to change the flush
plan must use
SessionExtension.before_flush.
However, there are several ways of
accomplishing what you want here
without modifiying the flush plan.
The simplest is what I already
suggested. Use
MapperExtension.before_insert() on the
"User" class, and set
user.question_count =
len(user.questions). This assumes
that you are mutating the
user.questions collection, rather than
working with Question.user to
establish the relationship. If you
happened to be using a "dynamic"
relationship (which is not the case
here), you'd pull the history for
user.questions and count up what's
been appended and removed.
The next way, is to do pretty much
what you think you want here, that is
implement after_insert on Question,
but emit the UPDATE statement
yourself. That's why "connection" is
one of the arguments to the mapper
extension methods:
def after_insert(self, mapper, connection, instance):
connection.execute(users_table.update().\
values(question_count=users_table.c.question_count +1).\
where(users_table.c.id==instance.user_id))
I wouldn't prefer that approach since
it's quite wasteful for many new
Questions being added to a single
User. So yet another option, if
User.questions cannot be relied upon
and you'd like to avoid many ad-hoc
UPDATE statements, is to actually
affect the flush plan by using
SessionExtension.before_flush:
class
MySessionExtension(SessionExtension):
def before_flush(self, session, flush_context):
for obj in session.new:
if isinstance(obj, Question):
obj.user.question_count +=1
for obj in session.deleted:
if isinstance(obj, Question):
obj.user.question_count -= 1
To combine the "aggregate" approach of
the "before_flush" method with the
"emit the SQL yourself" approach of
the after_insert() method, you can
also use SessionExtension.after_flush,
to count everything up and emit a
single mass UPDATE statement with many
parameters. We're likely well in the
realm of overkill for this particular
situation, but I presented an example
of such a scheme at Pycon last year,
which you can see at
http://bitbucket.org/zzzeek/pycon2010/src/tip/chap5/sessionextension.py
.
And, as I tried, I found we should update the user.question_count in after_flush
user, being I assume a RelationshipProperty, is only populated after the flush (as it is only this point the ORM knows how to relate the two rows).
It looks like question_count is actually a derived property, being the number of Question rows for that user. If performance is not a concern, you could use a read-only property and let the mapper do the work:
#property
def question_count(self):
return len(self.questions)
Otherwise you're looking at implementing a trigger, either at the database-level or in python (which modifies the flush plan so is more complicated).

Resources