Rails and mongo embed relations - ruby-on-rails

I'm trying to create some relations on Mongoid but when I try to save the inner object or add it to the user.personal_accounts collection I get the following error
NoMethodError: undefined method `bson_type' for #<Bank:0x71c01a8>
My Object in rails console is correct
#<PersonalAccount _id: 56e87f669c27691be0d3041b, number: "55", active: true, bank: #<Bank _id: 56d74cdb9c27692fb4bd4c6d, code: 123, name: "Bradesco", country: "USA">>
My mappings
class PersonalAccount
include Mongoid::Document
field :number, type: String
field :active, type: Boolean
field :bank, type: Bank
embedded_in :user
end
class User
include Mongoid::Document
field :first_name, type: String
field :last_name, type: String
embeds_many :personal_accounts
end
class Bank
include Mongoid::Document
field :code, type: Integer
field :name, type: String
field :country, type: String
end
The mapping that I was expecting is:
User
PersonalAccounts
Bank
Bank
As I have read that I need to copy the outer bank to each PersonalAccount.
I have already tried the following Link
Versions installed:
bson (4.0.2)
bson_ext (1.5.1)
mongoid (5.0.2)
mongo (2.2.4)

The root of your problem is right here:
field :bank, type: Bank
MongoDB doesn't know how to store a Bank so Mongoid will try to convert it to something that MongoDB will understand while Mongoid is preparing the data for the database, hence the NoMethodError.
Presumably you want Bank to exist as its own collection and then each PersonalAccount would refer to a Bank. That would be a standard belongs_to setup:
class PersonalAccount
#... but no `field :bank`
belongs_to :bank
end
That will add a field :bank_id, :type => BSON::ObjectId to PersonalAccount behind the scenes and hook up accessor (bank) and mutator (bank=) methods for you.
Normally you'd want the other half of the relation in Bank:
class Bank
#...
has_many :personal_accounts
end
but that won't work (as you found out) because PersonalAccount is embedded inside User so Bank can't get at it directly. Keep in mind that embeds_one is just a fancy of wrapping the Mongoid machinery around a Hash field in a document and embeds_many is just a fancy way of wrapping the Mongoid machinery around an array of hashes inside another document; embedded documents don't have an independent existence, they're just a part of their parent.

Related

Connecting Rails app to existing Mongodb database

I have a mongodb database with a single collection containing 400+ entries of basic data.
I'm using Rails and the mongoid gem to link the two together however when I query my model in the rails console there are no entries found.
QuizQuestion.first
Yields no results
My model:
class QuizQuestion
include Mongoid::Document
field :question, type: String
field :correctAnswer, type: String
field :wrongAnswers, type: Array, default: []
field :category, type: String
end
I have configured the mongoid.yml configuration file to point to the address of the database.
Does anyone know how to correctly do this or where I'm going wrong?
The reasons why you see no result:
1) database config is incorrect and you are pointing to a different database on the same mongodb instance
2) class name does not match the name for the collection within mongo. Open up a console/terminal and type:
mongo
then type this:
show dbs
This is the name of the dbs you need in the first part
use x
Where x is the db name
show collections
This will list the names of the collections.
Once you have the name of your collections, you can add this to your model:
store_in collection: "name_of_collection_as_in_mongo"
Therefore if the name of your collection was quiz_question as shown in the mongo client you can do this on your model:
class QuizQuestion
include Mongoid::Document
store_in collection: "quiz_question"
field :question, type: String
field :correctAnswer, type: String
field :wrongAnswers, type: Array, default: []
field :category, type: String
end
The reason you are not seeing any records (if you are pointing at the correct db name) is most likely due to mongoid expecting the class name to equal a pluralised collection name so QuizQuestions == quiz_questions within mongo

How can you update/destroy one embed document that is inside of all documents on a collection

My application model allows Patients to have CustomFields. All patients have the same customs fields. Customs fields are embedded in the Patient document. I should be able to add, update and remove custom fields and such actions are extended to all patients.
class Patient
include Mongoid::Document
embeds_many :custom_fields, as: :customizable_field
def self.add_custom_field_to_all_patients(custom_field)
Patient.all.add_to_set(:custom_fields, custom_field.as_document)
end
def self.update_custom_field_on_all_patients(custom_field)
Patient.all.each { |patient| patient.update_custom_field(custom_field) }
end
def update_custom_field(custom_field)
self.custom_fields.find(custom_field).update_attributes({ name: custom_field.name, show_on_table: custom_field.show_on_table } )
end
def self.destroy_custom_field_on_all_patients(custom_field)
Patient.all.each { |patient| patient.remove_custom_field(custom_field) }
end
def remove_custom_field(custom_field)
self.custom_fields.find(custom_field).destroy
end
end
class CustomField
include Mongoid::Document
field :name, type: String
field :model, type: Symbol
field :value, type: String
field :show_on_table, type: Boolean, default: false
embedded_in :customizable_field, polymorphic: true
end
All pacients have the same customs fields embedded in. Adding a custom field works very well. My doubt is about updating and destroying.
This works, but it is slow. It makes a query for each pacient. Ideally I would just be able to say to MongoDB 'update the document with id: that is embedded in the array *custom_fields* for all documents in the Patient collection'. Idem for destroy.
How can I do this in Mongoid?
I am using Mongoid 3.1.0 & Rails 3.2.12
I don't think there is a way you can do that with a good efficiency with embedded documents.
Maybe you should consider having a referenced relationship between your models, so that you can use the delete_all and update_all methods on the collection.

Generate a ruby class in memory

I have a requirement to convert an ActiveRecord model class into a MongoDB Document class automatically. I am able to do so using a rails generator which will read the attributes of a model and generate the new document.rb.
If a ActiveRecord model class looks like below:
class Project < ActiveRecord::Base
attr_accessible :completed, :end_date, :name, :start_date
end
Then, a generated class confirming to Mongoid's structure will be as below:
class ProjectDocument
field :name, type: String
field :start_date, type: Date
field :end_date, type: Date
field :completed, type: Boolean
field :created_at, type: Time
field :updated_at, type: Time
end
But I don't want to store a different document files, one for each model. I want to be able to generate this document class on the fly, whenever the rails application is started.
Is this possible? Is this approach of generating and using classes from memory advised? I don't have constraints on changes to AR model structure; the document is flexible w.r.t data structure and changed columns will get added automatically.
My first attempt would look something like this:
klass = Project
new_class = Object.const_set(klass.name + "Document", Class.new)
klass.columns.each do |c|
new_class.class_eval do
field c.name.to_sym, type: c.type
end
end
You'll almost certainly have to do something more complicated to set the field type correctly, but this should give you a good starting point.

Custom fields with mongoid

I started to use the mongoid gem in my project, and I'm a little confused about how it store and get the information on the database. I have fields of specifics types in my models, but when I get it from the DB it returns a Hash.
Here is my models:
service.rb
class Service
include Mongoid::Document
field :username, type: String
field :strategy, type: Strategy
field :design, type: Design
end
strategy.rb
class Strategy
include Mongoid::Document
field :name, type: String
field :description, type: String
field :resources, type: Resources
field :scalability, type: Scalability
field :localization, type: Localization
field :contact, type: Contact
end
If I initialize a new service #service, and do #service.class it returns Service, the right one, but if I try do #service.strategy.class, it returns Hash, and not Strategy, like I was expecting. I read on the mongoid manual there are the "Custom field serialization", what I think allows me to do what I want. But I was wondering if there are not any other way to do that easily, because I have lots of models to change.

Rails 3 and Mongoid: Embedded documents validation

So, I am having some issues with user authentication in embedded documents. I have two documents, one embedded in the other. A business has many members. The models look like this:
class Member
include Mongoid::Document
field :username, type: String
field :password, type: String
embedded_in :business
validates :username, :presence => true, :uniqueness => true, :length => 5..60
end
class Business
include Mongoid::Document
field :name, type: String
embeds_many :members
end
The problem is that it isn't validating the username's uniqueness in each model. When I save a member within a business, I can save a thousand of the same name. This of course is not going to work for a good authentication system. I am using Mongoid 2, Rails 3, and Ruby 1.9
This is a normal behavior when using embedded documents as explained here: MongoID validation
validates_uniqueness_of
Validate that the field is unique in the database: Note that for
embedded documents, this will only check that the field is unique
within the context of the parent document, not the entire database.
I think you want to try to create an Index in the username field that would ensure uniqueness among all the objects of that collection. Something like this:
ensureIndex({username:1},{unique:true});
EDIT: If you want Mongo to throw exception if a document with the same index value exists, you must avoid Mongo to do the “fire and forget” pattern. This means that the database will not wait for a response when you perform an update/write operation on a document.
And you want to pass this parameter: safe:true. By doing so Mongo should raise an exception if for any reason the document can't be inserted.

Resources