Querying embedded documents on a document with MongoMapper - ruby

What is a good pattern for querying embedded documents on a document? For instance, my User document has an embedded Alerts document. If I want to see if a given User has an alert with name I can do it two ways as far as I can tell -- in memory a la
alert = current_user.alerts.select{|a| a.name == params[:name]}.first
or via the actual document interface a la (note that I'm not 100% sure this is semantically valid but you get the point):
User.where('alerts.name' => params[:name], :id => current_user.id).first
There MUST be a better way, something like
current_user.alerts.where(:name => params[:name])
perhaps? Or maybe I'm just not thinking about the problem right?

Nope. And I think this is the motivation:
In MongoMapper, queries on the database always return a root object. Allowing queries to return an embedded doc without its parent would be a break with that and make a lot of things more complicated (what if I call .parent inside that embedded doc?) so MongoMappers errs on the side of simplicity and doesn't pretend that things are something they aren't. Embedded docs are stored in an array inside the root doc in MongoDB, so MongoMapper gives you an array in Ruby.
So your two ways of doing it are the intended ways of doing it.
If you need some syntactic suger, it shouldn't be too hard to code up. You could extend Array or you could code a plugin to expand upon MongoMapper's proxy for embedded docs.

I think Mongoid supports this, see "Finding" in the manual for embedded docs.

You can do either:
User.where('alerts.name' => params[:name], :id => current_user.id).fields(:alerts).first.alerts.select{|u| u.name == params[:name]}
or
User.where('alerts.name' => params[:name], :id => current_user.id).fields(:alerts).alerts.select{|u| u.name == params[:name]}.first

Related

DataMapper use only certain columns

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

Is it possible to drill down to an element with page object

I'm trying to use Cheezy's page-object gem for everything in order to be consistent. However I haven't been able to find how to drill down to an element like this. The situation here is that there would be more than one link with all the same tags so you have to drill down from something identifiable.
#browser.p(:text => /#{app_name}/i).link(:text => 'Add').click
The code I'm looking for would be something like this to click on a link located inside of a paragraph but it doesn't work.
p(:pgraph, id: => 'some-pgraph')
link(:lnk, text: => 'add')
self.pgraph.lnk
Is there a way to do this with page object?
Thanks,
Adam
You can use blocks to define accessors with more complicated locating strategies.
If you want to also keep a reference to the paragraph:
p(:pgraph, id: 'some-pgraph')
link(:lnk){ pgraph_element.link_element(text: 'add') }
Or if you do not need the paragraph for other things, you might do:
link(:lnk){ paragraph_element(id: 'some-pgraph').link_element(text: 'add') }
Basically you can use a block with nested elements, to define accessors similar to how you would in Watir.
Note that if you want to specify the id dynamically at run time, you can always define a method to click the link instead of using the accessors:
def click_link_in(paragraph_id)
paragraph_element(id: paragraph).link_element(text: 'add').click
end

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.

Case insensitive uniqueness validation in a Ruby Sequel.migration

I'm trying to figure out a good validation to use in my migration that will require case-insensitive uniqueness for user email addresses. In short, I want something like validate :email, :uniqueness => {:case_sensitive => false} without having to convert everything to use Rails or ActiveRecord. I could run emails through regexes but I don't like that solution.
I found a comment[1] saying you could use
validates_unique(:email){ |ds| ds.opts[:where].args.map! { |x| Sequel.function(:lower, x)}; ds}
but I don't understand what that code is doing and I don't want to use that code when I have no idea what that ds object is or what all is going on (why map!, does postgresql have a Sequel.function of :lower? ... probably, but I just don't know.)
[1] http://comments.gmane.org/gmane.comp.lang.ruby.sequel/6447
So I need one of two things answered:
1) How do I perform a case-insensitive uniqueness validation in a pure Sequel.migration (no ActiveRecord, no Rails)?
- OR -
2) If that code snippet I found online is actually what I want, what does it do & how does it work? (What is the ds object and what does this validation do with my database?)
As the Tin Man mentioned, you are confusing validations and constraints. You say you are trying to add a constraint and talk about Sequel.migration, but those have nothing to do with validations.
If you want to add a database constraint, you need to do something like this in a migration:
alter_table(:table){add_unique_constraint Sequel.function(:lower, :email)}
This is done so that the database doesn't allow duplicate emails in a case insensitive manner.
Validations are just for presenting nice error messages to the user. They are run before saving so that instead of the database raising a exception (which is difficult to deal with), you get a nice error message.
Like that comment mentions, you can't use validates_unique for case insensitive lookups on case sensitive databases without a hack. It would require that validates_unique accept an additional option (which may be added in the future).
If you don't want to use a hack like that, you'll have to do the validation manually:
dataset = model.where{|o| {o.lower(:email)=>o.lower(email)}}
dataset.exclude(pk_hash) unless new?
errors.add(:email, 'is already taken') unless ds.count == 0
In terms of what that hack does, ds is a Sequel::Dataset instance that validates_unique uses to check for uniqueness. If you do validates_unique :email, it'll be something like:
model.where(:email=>email)
# WHERE email = 'some email'
ds.opts[:where] extracts the where clause from that dataset, and transforms the arguments, wrapping them in SQL lower function calls, in order to transform the where clause so that it is similar to:
model.where{|o| {o.lower(:email)=>o.lower(email)}}
# WHERE lower(email) = lower('some email')
It's a hack as it only works if the model's dataset is not already filtered.

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.

Resources