Full text search in embedded one to many, MongoID child docs - ruby-on-rails

I have a MongoID User model
class User
include Mongoid::Document
field :name
embeds_many :watchlists
end
which embedded one to many
class Watchlist
include Mongoid::Document
field :description
embedded_in :user
end
I want to search full text into :description field which is in Watchlist child embedded in User. The most heavy search goes through 1.5k descriptions, with a max of 30, 40 words each.
The constrain here, is that I'll deploy on Heroku and they have not free plan for indexing currently.
Then I've tried with mongoid_fulltext (as well as mongoid_search and mongoid-searchable) but without success.
Does anybody have an idea how to do that ?
UPDATE:
This is an example of mongoid_fulltext. The User model embeds many Watchlist(s). I'm searcing a string in :description field, which is in Watchlist child doc :
class Watchlist
include Mongoid::Document
field :description
...
embedded_in :user
end
Watchlist is embedded in User :
class User
include Mongoid::Document
include Mongoid::FullTextSearch
field :name
...
embeds_many :watchlists
def search_in_description
self.watchlists.map{ |w| w.description }.join(' ')
end
fulltext_search_in :search_in_description
end
... but in that way, running User.fulltext_search("a presentation framework based on the power of CSS3") gives me back just the parent doc causing the match (a user instance) and not the watchlists doc ( the child instance ).
See the output: http://pastie.org/3226179
How can I get just the matching "watchlists"? ( I tryed few ways without success )

In MongoDB you cannot query directly for embedded documents. You can query they way you explained in the example to get the parent document, then query on the returned top document user for the desired embedded document among current user watchlists.
If you're going to do that frequently, perhaps you might want to consider using relational has_many rather than embeds_many.

Related

Mongoid: How do I query for all object where the number of has_many object are > 0

I have a Gift model:
class Gift
include Mongoid::Document
include Mongoid::Timestamps
has_many :gift_units, :inverse_of => :gift
end
And I have a GiftUnit model:
class GiftUnit
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :gift, :inverse_of => :gift_units
end
Some of my gifts have gift_units, but others have not. How do I query for all the gifts where gift.gift_units.size > 0?
Fyi: Gift.where(:gift_units.exists => true) does not return anything.
That has_many is an assertion about the structure of GiftUnit, not the structure of Gift. When you say something like this:
class A
has_many :bs
end
you are saying that instance of B have an a_id field whose values are ids for A instances, i.e. for any b which is an instance of B, you can say A.find(b.a_id) and get an instance of A back.
MongoDB doesn't support JOINs so anything in a Gift.where has to be a Gift field. But your Gifts have no gift_units field so Gift.where(:gift_units.exists => true) will never give you anything.
You could probably use aggregation through GiftUnit to find what you're looking for but a counter cache on your belongs_to relation should work better. If you had this:
belongs_to :gift, :inverse_of => :gift_units, :counter_cache => true
then you would get a gift_units_count field in your Gifts and you could:
Gift.where(:gift_units_count.gt => 0)
to find what you're looking for. You might have to add the gift_units_count field to Gift yourself, I'm finding conflicting information about this but I'm told (by a reliable source) in the comments that Mongoid4 creates the field itself.
If you're adding the counter cache to existing documents then you'll have to use update_counters to initialize them before you can query on them.
I tried to find a solution for this problem several times already and always gave up. I just got an idea how this can be easily mimicked. It might not be a very scalable way, but it works for limited object counts. The key to this is a sentence from this documentation where it says:
Class methods on models that return criteria objects are also treated like scopes, and can be chained as well.
So, get this done, you can define a class function like so:
def self.with_units
ids = Gift.all.select{|g| g.gift_units.count > 0}.map(&:id)
Gift.where(:id.in => ids)
end
The advantage is, that you can do all kinds of queries on the associated (GiftUnits) model and return those Gift instances, where those queries are satisfied (which was the case for me) and most importantly you can chain further queries like so:
Gift.with_units.where(:some_field => some_value)

Modeling product variations in Rails3/Mongoid with an embeds_many / embedded_in relation....?

I know this is a (somewhat) common question but I'm a newbie and I always appreciate a second (or 100th) pair of eyes on a problem I'm been noodling.
I'm wondering if I'm taking the right approach here -- I'm trying to model a product with several variations and the associated gift certificates sold against that product.
A product, for example, for a restaurant might be a "tasting menu for 2" whereas variations might be "food only", "with partial wine pairing", and "with full wine pairing", etc. (just an illustrative example).
Here's a distilled version of what I've come up with:
class Product
include Mongoid::Document
field :name, type: String
has_many :gift_certificates
embeds_many :variations
end
class Variation
include Mongoid::Document
field :name, type: String
embedded_in :product
has_many :gift_certificates
end
class GiftCertificate
include Mongoid::Document
field :recipient, type: String
field :amount, type: Float
belongs_to :product
belongs_to :variation
end
So when we create a GiftCertificate, it's defined as belonging to both a product and a variation (is that correct?). An example might be a "$200 gift certificate for tasting menu for 2 [product] with partial wine pairing [variation]", etc.
I feel like the embeds_many / embedded_in relation here for Products/Variations is ideal as it fits the profile for when it should be used: I'll never be accessing a product without its variation, and I never need to access a variation independant of a product -- they go hand-in-hand.
So the question is whether I'm doing this the right way.
I've managed to massage the basic CRUD forms into working using ryanb's excellent nested_form gem, with a bit of tweaking. Editing the Products & Variations are easy (the basic use case), but the trouble I'm having comes when working with the Gift Certificates.
Mongoid is giving me a bit of trouble ... the GiftCertificate belonging to both a product and its variation seems kludgy, and I have to manually set the GiftCertificate.variation each time I do a find, i.e.
#gc = GiftCertificate.find(params[:id]) // as normal
#gc.variation // ==> nil, doesn't work by itself
#gc.variation = #gc.product.variations.find(#gc.variation_id) // Have to manually set this
#gc.variation // Now this works as it's supposed to
Anyway, before I go on -- would love to get some thoughts on the above.
Thanks in advance!

Ruby Sunspot Keyword search with IDs

Simple question:
I've got two models: Show and Contact. What I'd like to do is search for either a Show#id or a Contact#name. So my models are set up as follows:
class Show < ActiveRecord::Base
searchable do
text :id
end
end
class Contact < ActiveRecord::Base
searchable do
text :name
end
end
Then I run searches as follows:
Sunspot.search(Contact, Show) do
keywords(MY_QUERY)
end
This works great for finding Contacts by their name, but never works with the Show#id field. Can anyone explain why this is?
Joe
The id field is an integer, so you should have integer :id in the model. You need to specify the correct type of database column. Check out the example at http://sunspot.github.com/.

how to overwrite embeds_many in inherited model in Mongoid?

I am using mongoid in Rails 3 and I came across this problem:
Let's say I have a Shape model:
class Shape
include Mongoid::Document
field :x, type: Integer
field :y, type: Integer
embedded_in :canvas
end
And a Canvas model (has many Shapes):
class Canvas
include Mongoid::Document
field :name, type: String
embeds_many :shapes
end
Then a Canvas model "has many Shapes".
I have Browser model inherited from Canvas:
class Browser < Canvas
field :version, type: Integer
end
Then Browswer model should "has many Shapes" now.
But, now I have a "Circle" model inherited from Shape:
class Circle < Shape
field :radius, type: Float
end
And I want to let Browser model to "has many Circles" instead of "has many Shapes". That is to say, I want to overwrite the "has many" relationship in Browser model from "has many Shapes" to "has many Circles".
How should I do it?
I'm not 100% sure, but I think you would just add the line for embeds_many :circles to the Browser model. You wouldn't need to remove the inherited relation.
Since Circle inherits from Shape, circles will get stored in an array stored in the "shapes" key in the Browser document anyway, they'll just have their _type attribute set to "Circle". In other words, having the embeds_many :shapes relation doesn't create anything in the DB that embedding many circles wouldn't create anyway.
It will, however, mean that you have methods such as Browser.frist.shapes available, but you can simply ignore these. Adding the embeds_many :circles will give you the methods for that relation, such as Browser.first.circles.

Relations with mongoid, what should i use?

I'm using Ruby on Rails 3.1 with mongoid and trying to set up som rather simple relations between posts, comments, users and tags.
I'm very new to mongodb, and no-sql in general so I'm a bit confused.
What I am trying to accomplish is this:
Users, posts and comments should be able to have multiple tags.
Tags should have name, type and a count of how many times it has been used.
I need to be able to get all available tags so that users kan choose from them.
And the other way around, be able to retrieve tags from users, posts and comments.
I've read a lot about it, and still can't seem to figure out which approach I should take. Should I use referencial or embedded relationships?
I've looked at a couple of gems but no-one seems to work as i described above.
Sidenote: I am going to use Tire for my search-function later on.
Cool, welcome to MongoDB! This is hard to get right and depends on your application but I'll try and give you some pointers based on what you've written and what I think will work best.
This isn't always the case but the general theory is that if an object is always manipulated and viewed in context of another you should embed it inside that object. This is probably the case with comments and posts in your application. Therefore you may want to embed comments inside posts.
However, because you use the tag object in multiple contexts I would make it its own collection like this:
class Tag
include Mongoid::Document
field :tag, type: String
field :type, type: String
field :count, type: Integer
end
Let's run down your requirements and build the models.
Tags should have name, type and a count of how many times it has been used.
Done via above code for Tag class.
Users, posts and comments should be able to have multiple tags.
Ok so let's give each of these classes a "tags" field that has an array of tag IDs, this would be a referential relationship.
class User
include Mongoid::Document
field :first_name, type: String
field :last_name, type: String
field :email, type: String
field :tags, type: Array
end
Also here we will embed comments inside of the posts along with having the array of tag IDs like we do for Users.
class Post
include Mongoid::Document
field :subject, type: String
field :body, type: String
field :tags, type: Array
embeds_many :comments
end
class Comment
include Mongoid::Document
field :name, type: String
field :type, type: String
field :count, type: Integer
embedded_in :post
end
Make sense? There is some more info here on modeling these sorts of relationships in Rails but using mongomapper instead of mongoid (so don't pay attention to the syntax but pay attention to the ideas presented)

Resources