Suppose the following model :
class Product
include MongoMapper::Document
key :name, String
key :product_category_id, ObjectId
belongs_to :product_category
end
class ProductCategory
include MongoMapper::Document
key :name, String, :required => true, :unique => true
timestamps!
userstamps!
end
I want to implement an advanced search that will inspect all value inside my Model including its all association like :
I have :
Product A data named "aLaptop" belongs_to:ProductCategory named "Notebook".
Product B data named "aGreatNotebook" belongs_to:ProductCategory named "Notebook".
When I search with keyword named "Notebook", I want to search it to Product.name fields and also its associations which mean ProductCategory.name also. So it will returned both of that items, because Product A have ProductCategory.name "Notebook" & Product B have Product.name "aGreatNotebook" and ProductCategory "Notebook"..
How can I do this?? I've already searching for 2 days and not success until now:(.. When is in MySQL, I used join table.. But hows in MongoMapper??
Please help.. Thank you..
You can't do joins in MongoDB. So the basic idea is to get the ObjectId associated with the "Notebook" category and then to query the products where product_category is equal to notebook_id. This generally involves two queries. So that'd be something like this:
notebook_id = ProductCategory.first(:name => "Notebook")
if notebook_id
Product.where({:product_category_id => notebook_id['_id']})
end
The question is confusing, but the title of the question is clear.
So in case someone comes here hoping to see how to get all of the:
keys
associations
Read on...
To get the keys in a model:
ConfinedSpace.keys.keys
=> ["_id", "photo_ids", "include_in_qap", "position", "created_at", "updated_at",
"structure_id", "identifier", "name", "description", "notes", "entry_info",
"anchor_points", "nature", "special_equipment", "rescue_overview"]
And to get the associations:
ConfinedSpace.associations.each{|name,assoc| puts name}
photos
attachments
activities
structure
videos
And the class (edited for brevity):
class ConfinedSpace
include MongoMapper::EmbeddedDocument
include Shared::HasPhotos
include Shared::HasAttachments
include HasActivities
TAG = "ConfinedSpace"
belongs_to :structure
many :videos, :as => :attachable
key :identifier, String
key :name, String
key :description, String
key :notes, String
key :entry_info, String
key :anchor_points, String
key :nature, String
key :special_equipment, String
key :rescue_overview, String
validates :identifier, presence: true
end
Related
When I do in rails console:
User.first
=> #<User _id: 6241f97de64d1eb2cc003d08,....
I get document, but when I try to find it by id like
User.find('6241f97de64d1eb2cc003d08') or User.find(BSON::ObjectId('6241f97de64d1eb2cc003d08')) or
User.find('6241f97de64d1eb2cc003d08'.to_bson) etc
I get:
message:
Document(s) not found for class User with id(s) 6241f97de64d1eb2cc003d08.
also
3.0.3 :049 > User.where(_id: BSON::ObjectId('6241f97de64d1eb2cc003d08')).find
=> nil
Why it does not work????
I use rails 7 and mongoid
Finally found the solution.
User model was defined like this:
class User
include Mongoid::Document
include Mongoid::Timestamps
field :_id, type: String
field :first_name, type: String
field :last_name, type: String
end
And changing it to this:
class User
include Mongoid::Document
include Mongoid::Timestamps
# field :_id, type: String
field :first_name, type: String
field :last_name, type: String
end
Commenting "field: _id" did the trick. I think string type was "covering" expected ObjectId as _id and find methods are converting strings into ObjectId so we ended up in search against string vs ObjectId.
I am attempting to order the results of a query by the value of a specific embedded document, but even with what seems to be a valid set of options and using the $elemMatch operator, my results are coming back in natural order.
My model is composed of Cards, which embeds_many :card_attributes, which in turn reference a specific CardAttributeField and contain an Integer value. I would like to be able to order a collection of Cards by that value.
I am able to isolate a collection of Cards which have a CardAttribute referencing a specific CardAttributeField like this:
cards = Card.where(:card_attributes.elem_match => {
:card_attribute_field_id => card_attribute_field.id
})
If I knew the order in which the card_attributes were set, I could use MongoDB array notation, like this:
cards.order_by(['card_attributes.0.value', :asc])
This does deliver my expected results in test scenarios, but it won't work in the real world.
After much messing around, I found a syntax which I thought would allow me to match a field without using array notation:
cards.asc(:'card_attributes.value'.elem_match => {
:card_attribute_field_id => card_attribute_field.id
})
This produced a set of options on the resulting Mongoid::Criteria which looked like:
{:sort=>{"{#<Origin::Key:0x2b897548 #expanded=nil, #operator=\"$elemMatch\", #name=:\"card_attributes.value\", #strategy=:__override__, #block=nil>=>{:card_attribute_field_id=>\"54c6c6fe2617f55611000068\"}}"=>1}}
However, the results here came back in the same order regardless or whether I called asc() or desc().
Is there any way to do what I'm after? Am I taking the wrong approach, or do I have a mistake in my implementation? Thanks.
Simplified, my model is:
class Card
include Mongoid::Document
# various other fields
has_many :card_attribute_fields
embeds_many :card_attributes do
def for_attribute_field card_attribute_field
where(:card_attribute_field_id => card_attribute_field.id)
end
end
end
class CardAttributeField
include Mongoid::Document
belongs_to :card
field :name, type: String
field :default_value, type: String
field :description, type: String
end
class CardAttribute
include Mongoid::Document
embedded_in :card
field :card_attribute_field_id, type: Moped::BSON::ObjectId
field :value, type: Integer
end
class User
include DataMapper::Resource
property :id, Serial
property :name, String
property :email, String
has n, :records
end
class Project
include DataMapper::Resource
property :id, Serial
property :name, String
has n, :records ?????
end
#
class Record
# SPEND_REGEX = /^[0-9]{1}:[0-5]{1}[0-9]{1}$/
include DataMapper::Resource
property :id, Serial
property :reporting_type, String
property :spend_time, String
belongs_to :user
belongs_to :project ????
end
DataMapper.auto_upgrade!
With ??? I marked relation that throws an error "`execute_non_query': Cannot add a NOT NULL column with default value NULL (DataObjects::SyntaxError)
"
How to define 2 has many relationships to one model in datamapper?
By default, your belongs_to relationships are required. I assume you already have Record entries in your database. The auto_upgrade is trying to add the new field for the association, and by default it marks that column as NOT NULL. However, for all the existing records, that value will be NULL.
To get around this, do one of the following:
Do an auto_migrate instead of auto_upgrade. This will blow away your data, but
will allow you to add the relationship columns without it choking on
NULL values.
Make the associations optional with :required => false. This will allow NULLs in the database. Next, go in and set those fields to the appropriate values. Lastly, modify the database table column to be NOT NULL.
I have the following model:
class User
include Mongoid::Document
store_in :users
field :full_name, :type => String
end
class Message
include Mongoid::Document
embeds_one :sender, :class_name => "User"
field :text, :type => String
end
I would like to store User and Message in separated standalone collections so that they could be queried directly, and I would like to have one copy of user for sender in each Message entry. Is my model correct for this kind of request?
And when I have an instance of User user, how could I query the messages where sender = user?
I've tried:
Message.where(:sender => user)
Message.where('sender.id' => user.id)
both not work.
only Message.where('sender.full_name' => user.full_name) worked, but I don't want to rely on a text field when there's an id field to use.
What's the best way to do that?
How I save Message/User:
user = User.new
user.full_name = 'larry'
user.save
m = Message.new(:text => 'a text message')
m.sender = user
m.save
And it results in the database:
> db.users.find({ 'full_name' : 'larry'})
> db.messages.find({})[0]
{
"_id" : ObjectId("4f66e5c10364392f7ccd4d74"),
"text" : "a text message",
"sender" : {
"_id" : ObjectId("4f62e0af03642b3fb54f82b0"),
"full_name" : "larry"
}
}
Like explain by Jordan Durran ( Mongoid lead developer ) in Google group of Mongoid : http://groups.google.com/group/mongoid/browse_thread/thread/04e06a8582dbeced#
You're going to need a separate model if you want to embed the user
data inside the message. When denormalizing like this I generally
namespace one of them, and create a module with the common fields to
include in both - maybe in your case you can call it Sender?
class Sender
include Mongoid::Document
include UserProperties
class << self
def from_user(user)
Sender.new(user.attributes)
end
end
end
class User
include Mongoid::Document
include UserProperties
end
module UserProperties
extend ActiveSupport::Concern
included do
field :full_name, type: String
end
end
class Message
include Mongoid::Document
embeds_one :sender
end
You also don't need the :store_in macro on User - by default it's name
would be "users".
You can't do what you do.
Your user document is save in his one collection because you use the store_in method. And you try save it on an other document ( Message)
If you really want 2 collections, you need use has_one :user in your Message class.
class Message
has_one :sender, :class_name => 'User', :foreign_key => 'sender_id'
end
After you can get your message like :
Message.senders to have all of your sender.
The Source code is
class RealTimeDetail
include MongoMapper::EmbeddedDocument
key :url, String
key :method, String
end
class TargetFeed
include MongoMapper::Document
key :name, String, :null => false
key :feed_type, String, :null => false
has_one :real_time_detail
end
When I do target_feed.real_time_detail = RealTimeDetail.new(:url => "http://example.com", :method => "get")
I get errored out.
Instead i've changed the TargetFeed to
class TargetFeed
include MongoMapper::Document
key :name, String, :null => false
key :feed_type, String, :null => false
key :real_time_detail, RealTimeDetail
end
This works but was wondering if this is the best way to go about it.
Your classes no longer inherit from ActiveRecord, and has_one belongs to ActiveRecord, so it's not available for use. Using an explicit key seems like it would work.
MongoMapper uses different syntax for implementing associations between data models. Details here: http://mongomapper.com/documentation/plugins/associations.html
the short of it is, instead of has_one, use one
class TargetFeed
include MongoMapper::Document
key :name, String, :null => false
key :feed_type, String, :null => false
one :real_time_detail
end
I believe that since you've defined RealTimeDetail as an embedded document you don't need to declare an association in the definition of RealTimeDetail. If RealTimeDetail included Document (instead of EmbeddedDocument) you would use belongs_to from it's end:
class RealTimeDetail
include MongoMapper::Document
key :url, String
key :method, String
belongs_to :target_feed
end