Trying to create a class method on an embedded doc to returns a single object

94 views
Skip to first unread message

Mitch

unread,
Jun 13, 2010, 5:08:16 PM6/13/10
to Mongoid
A website embeds pages and Page has a find_by_slug finder method.

class Website
include Mongoid::Document
embeds_many :pages
end

class Page
include Mongoid::Document
embedded_in :website, :inverse_of => :pages

...

def self.find_by_slug(slug)
self.where(:slug => slug).first
end
end

If I try to use my class method...

@page = Website.first.pages.find_by_slug('home')

I get an "undefined method `documents=' for Nil" error. I think I
understand why this isn't possibly given the lazy loading of these
criteria queries. However, I was wondering if there was anyway I
could create a class "finder" method like this.

For now my workaround is:

class Page
include Mongoid::Document
embedded_in :website, :inverse_of => :pages

...

def self.with_slug(slug)
self.where(:slug => slug)
end
end

@page = Website.first.pages.with_slug('home').first

HP

unread,
Jun 14, 2010, 1:07:11 AM6/14/10
to Mongoid
Hi Mitch,

I have not gotten criteria to work on embedded docs in my project, it
has benn a while since i tried. This is what I do:


class Website
# ...

embeds_many :pages do
def find_by_slug(slug)
@target.detect { |t| t.slug == slug }
end
end
end

@page = Website.first.pages.find_by_slug('slug')


It works. Criteria and scopes on embedded documents would be nice,
though.

HP

Durran Jordan

unread,
Jun 16, 2010, 9:48:38 PM6/16/10
to mon...@googlegroups.com
Criteria#first returns an actual Document or nil, so you cannot chain
it - so your workaround is correct. (It's not actually a workaround,
its by design.) My suggestion for getting the first one would be to
use the limit method instead:

def self.find_by_slug(slug)
where(:slug => slug).limit(1)
end

That will give you a chainable criteria back with what you are expecting.

Mitch

unread,
Jun 19, 2010, 2:50:57 PM6/19/10
to Mongoid
Thanks for the explanation on this. The only issue with adding
limit(1) is that this will return an array and what I am expecting
(from an ActiveRecord mindset) is a single Page. I think it would be
cool if your could have a criteria like 'first' which would tell a
Criteria to only return a single object instead of an array of
objects. Then I could do:

def self.find_by_slug(slug)
self.where(:slug => slug).first
end

Looking at the commit history of Mongoid it looks there used to be
functionality to add a :type option (:all, :first, :last) to a
Criteria. Is there anyway to get a criteria chain to return a single
document now?

On Jun 16, 9:48 pm, Durran Jordan <dur...@gmail.com> wrote:
> Criteria#first returns an actual Document or nil, so you cannot chain
> it - so your workaround is correct. (It's not actually a workaround,
> its by design.) My suggestion for getting the first one would be to
> use the limit method instead:
>
> def self.find_by_slug(slug)
>   where(:slug => slug).limit(1)
> end
>
> That will give you a chainable criteria back with what you are expecting.
>
Reply all
Reply to author
Forward
0 new messages