has_one vs. defining as a Key for Embedded documents for MongoMapper and MongoDB - mongodb

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

Related

RAILS Mongoid finding document by id

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.

How to: Single Table Inheritance in DataMapper?

I'm learning Sinatra (1.3.2) and chose to use DataMapper (1.2.0) as ORM and an in-memory SQLite (1.3.6) DB to start.
Two models, Books and Downloads, are sharing most attributes, so I looked into declaring a model for STI (Single Table Inheritance) in DataMapper. Reading the docs, this seems a piece of cake thanks to Types::Discriminator.
I abstracted all common ones into DownloadableResource:
class DownloadableResource
include DataMapper::Resource
property :id, Serial
property :created_at, DateTime
property :modified_at, DateTime
property :active, Boolean, default: true
property :position, Integer
property :title, String, required: true
property :url, URI, required: true
property :description, Text, required: true
property :type, Discriminator
end
Following the example, I thought it's just as easy as specifying what needs to be extended:
class Book < DownloadableResource
property :cover_url, URI
property :authors, String, required: true, length: 255
end
and
class Download < DownloadableResource
property :icon_url, URI
end
but this was giving me the following error:
DataObjects::SyntaxError: duplicate column name: id (code: 1, sql state: , query: ALTER TABLE "downloadable_resources" ADD COLUMN "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, uri: sqlite3::memory:?scheme=sqlite&user=&password=&host=&port=&query=&fragment=&adapter=sqlite3&path=:memory:)
while removing the id generated another (obvious) error:
DataMapper::IncompleteModelError: DownloadableResource must have a key to be valid
I got around this by adding include DataMapper::Resource to both Book and Download, and then Book needed a key to be valid, now looking like this:
class Book < DownloadableResource
include DataMapper::Resource
property :id, Serial
property :cover_url, URI
property :authors, String, required: true, length: 255
end
Same goes for Download, but now the issue is:
DataObjects::SyntaxError: duplicate column name: id (code: 1, sql state: , query: ALTER TABLE "books" ADD COLUMN "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, uri: sqlite3::memory:?scheme=sqlite&user=&password=&host=&port=&query=&fragment=&adapter=sqlite3&path=:memory:)
Starting to feel like I'm going in circles, what's the proper way to implement Single Table Inheritance in DataMapper?
PS: I have looked at
DataMapper - Single Table Inheritance and
Ruby Datamapper table inheritance with associations
but I still have this problem.
I would recommend this approach:
module DownloadableResource
def self.included base
base.class_eval do
include DataMapper::Resource
property :created_at, DateTime
property :modified_at, DateTime
property :active, base::Boolean, default: true
property :position, Integer
property :title, String, required: true
property :url, base::URI, required: true
property :description, base::Text, required: true
property :type, base::Discriminator
end
end
end
class Book
include DownloadableResource
property :id, Serial
# other properties
end
class Download
include DownloadableResource
property :id, Serial
# other properties
end

Get all keys in MongoMapper Model including its association

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

`execute_non_query': Cannot add a NOT NULL column with default value NULL (DataObjects::SyntaxError)

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.

mongoid - how to query by embedded object

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.