Model embeds_many and embedded_in relationship using factorygirls - ruby-on-rails

I want to write FactoryGirl class for creating company. The models are as follows:
module Company
class Contact
include Mongoid::Document
include ActiveModel::Validations
embedded_in :company, class_name: "::Company::Contact"
end
end
module Company
class Company
require 'autoinc'
embeds_many :contacts, class_name: "::Company::Contact"
end
end
FactoryGirl.define do
factory :company, :class => 'Company::Company' do
name { Faker::Company.name }
after(:create) do |company|
# company.contacts << create(:company_contact)
create_list(:company_contact, 1, company: company)
end
# contacts { [ build(:company_contact) ] }
end
end
The error received is
Failure/Error: create_list(:company_contact, 1, company: company)
Mongoid::Errors::InvalidPath:
message:
Having a root path assigned for Company::Contact is invalid.
summary:
Mongoid has two different path objects for determining the location of a document in the database, Root and Embedded. This error is raised when an embedded document somehow gets a root path assigned.
resolution:
Most likely your embedded model, Company::Contact is also referenced via a has_many from a root document in another collection. Double check the relation definitions and fix any instances where embedded documents are improperly referenced from other collections
How should I handle this? I cannot change the models.

You have the association class name wrong:
module Company
class Contact
include Mongoid::Document
include ActiveModel::Validations
embedded_in :company, class_name: 'Company::Company'
end
end

Related

Mongoid Association Creating (unwanted) Records

I'm at a loss to why Mongoid is creating a new record in an association. I'm stepping closely through the code, but I've never seen anything like this. I've made a test and slimmed down the code. I left the VCR in just in case it might be related.
it "should not create a duplicate entry for MT" do
state = PolcoGroup.create(type: :state, name: 'MT', active: true)
s = state.get_senators
state.junior_senator = s[:state_junior_senator] # !!!!! this creates a new record
state.senior_senator = s[:state_senior_senator] # !!!!! so does this line
expect(Legislator.all.size).to eql(2) # actually equals 4 -- each association creates a new record
end
result is:
Legislator.all.map(&:sortname)
=> ["Tester, Jon (Sen.) [D-MT]", "Walsh, John (Sen.) [D-MT]", "Walsh, John (Sen.) [D-MT]", "Tester, Jon (Sen.) [D-MT]"]
## models
class PolcoGroup
include Mongoid::Document
include Mongoid::Timestamps
include VotingMethods
include DistrictMethods
extend DistrictClassMethods
include StateMethods
field :name, :type => String
...
# STATE RELATIONSHIPS -----------------------------
has_one :junior_senator, class_name: "Legislator", inverse_of: :jr_legislator_state
has_one :senior_senator, class_name: "Legislator", inverse_of: :sr_legislator_state
...
end
class Legislator
include Mongoid::Document
include Mongoid::Timestamps
# the following fields are directly from govtrack
field :govtrack_id, type: Integer
field :bioguideid, type: String
...
belongs_to :jr_legislator_state, class_name: "PolcoGroup", inverse_of: :junior_senator
belongs_to :sr_legislator_state, class_name: "PolcoGroup", inverse_of: :senior_senator
...
end
module StateMethods
def get_senators
...
# just returns the following
{state_senior_senator: senators.first, state_junior_senator: senators.last}
end
end
You can see more code here: https://gist.github.com/tbbooher/d892f5c234053990da70
OK -- never do what I did. I was pulling in an old version of mongo as a test database and then conducting the above. Of course it wasn't working correctly.

How to migrate from belongs_to, to embedded_in in Mongoid?

If one first build their models with a belong_to and has_many association and then realized they need to move to a embedded_in and embeds_many association, how would one do this without invalidating thousands of records? Need to migrate them somehow.
I am not so sure my solution is right or not. This is something you might try to accomplish it.
Suppose You have models - like this
#User Model
class User
include Mongoid::Document
has_many :books
end
#Book Model
class Book
include Mongoid::Document
field :title
belongs_to :user
end
At first step I will create another model that is similar to the Book model above but it's embedded instead of referenced.
#EmbedBook Model
class EmbedBook
include Mongoid::Document
field :title
embedded_in :user
end
#User Model (Update with EmbedBook Model)
class User
include Mongoid::Document
embeds_many :embed_books
has_many :books
end
Then create a Mongoid Migration with something like this for the above example
class ReferenceToEmbed < Mongoid::Migration
def up
User.all.each do |user|
user.books.each do |book|
embed_book = user.embed_books.new
embed_book.title = book.title
embed_book.save
book.destroy
end
end
end
def down
# I am not so sure How to reverse this migration so I am skipping it here
end
end
After running the migration. From here you can see that reference books are embedded, but the name for the embedded model is EmbedBook and model Book is still there
So the next step would be to make model book as embed instead.
class Book
include Mongoid::Document
embedded_in :user
field :title
end
class User
include Mongoid::Document
embeds_many :books
embeds_many :embed_books
end
So the next would be to migrate embedbook type to book type
class EmbedBookToBook < Mongoid::Migration
def up
User.all.each do |user|
user.embed_books.each do |embed_book|
book = user.books.new
book.title = embed_book.title
book.save
embed_book.destroy
end
end
def down
# I am skipping this portion. Since I am not so sure how to migrate back.
end
end
Now If you see Book is changed from referenced to embedded.
You can remove EmbedBook model to make the changing complete.
This is just the suggestion. Try this on your development before trying on production. Since, I think there might be something wrong in my suggestion.
10gen has a couple of articles on data modeling which could be useful:
Data Modeling Considerations for MongoDB Applications
Embedded One-to-Many Relationships
Referenced One-to-Many Relationships
MongoDB Data Modeling and Rails
Remember that there are two limitations in MongoDB when it comes to embedding:
the document size-limit is 16MB - this implies a max number of embedded documents, even if you just embed their object-id
if you ever want to search across all embedded documents from the top-level, then don't embed, but use referenced documents instead!
Try these steps:
In User model leave the has_many :books relation, and add the
embedded relation with a different name to not override the books
method.
class User
include Mongoid::Document
has_many :books
embeds_many :embedded_books, :class_name => "Book"
end
Now if you call the embedded_books method from a User instance
mongoid should return an empty array.
Without adding any embedded relation to Book model, write your own
migration script:
class Book
include Mongoid::Document
field :title, type: String
field :price, type: Integer
belongs_to :user
def self.migrate
attributes_to_migrate = ["title","price"] # Use strings not symbols,
# we keep only what we need.
# We skip :user_id field because
# is a field related to belongs_to association.
Book.all.each do |book|
attrs = book.attributes.slice(*attributes_to_migrate)
user = book.user // through belong_to association
user.embedded_book.create!(attrs)
end
end
end
Calling Book.migrate you should have all the Books copied inside each user who was
associated with belongs_to relation.
Now you can remove the has_many and belongs_to relations, and
finally switch to clean embedded solution.
class User
include Mongoid::Document
embeds_many :books
end
class Book
include Mongoid::Document
field :title, type: String
field :price, type: Integer
embedded_in :user
end
I have not tested this solution, but theoretically should work, let me know.
I have a much shorter concise answer:
Let's assume that you have the same models:
#User Model
class User
include Mongoid::Document
has_many :books
end
#Book Model
class Book
include Mongoid::Document
field :title
belongs_to :user
end
So change it to embeds:
#User Model
class User
include Mongoid::Document
embeds_many :books
end
#Book Model
class Book
include Mongoid::Document
field :title
embedded_in :user
end
And generate a mongoid migration like this:
class EmbedBooks < Mongoid::Migration
##attributes_to_migrate = [:title]
def self.up
Book.unscoped.where(:user_id.ne => nil).all.each do |book|
user = User.find book[:user_id]
if user
attrs = book.attributes.slice(*##attributes_to_migrate)
user.books.create! attrs
end
end
end
def self.down
User.unscoped.all.each do |user|
user.books.each do |book|
attrs = ##attributes_to_migrate.reduce({}) do |sym,attr|
sym[attr] = book[attr]
sym
end
attrs[:user] = user
Book.find_or_create_by(**attrs)
end
end
end
end
This works because when you query from class level, it is looking for the top level collection (which still exists even if you change your relations), and the book[:user_id] is a trick to access the document attribute instead of autogenerated methods which also exists as you have not done anything to delete them.
So there you have it, a simple migration from relational to embedded

How to load Factories for Embedded Models (Mongoid - Rails)

I am using Mongoid in my Rails application, consider i have the below fields in a class named "Post" with below structure
class UserPost
include Mongoid::Document
field :post, type: String
field :user_id, type: Moped::BSON::ObjectId
embeds_many :comment, :class_name => "Comment"
validates_presence_of :post, :user_id
end
-
class Comment
include Mongoid::Document
field :commented_user_id, type: Moped::BSON::ObjectId
field :comment, type: String
embedded_in :user_post, :class_name => "UserPost"
end
This model works perfect when inserting values.
But now i am working on writing test for this model, i am using Factory girl to load test data. I am confused with how i can plot out model fields for "UserPost" model under
/spec/factories/user_posts.rb.
I tried with below format, but its not working (only some fields are added for example)
FactoryGirl.define do
factory :user_post do
id Moped::BSON::ObjectId("50ffd609253ff1bfb2000002")
post "Good day..!!"
user_id Moped::BSON::ObjectId("50ffd609253ff1bfb2000002")
comment :comment
end
factory :comment do
id Moped::BSON::ObjectId("50ffd609253ff1bfb2000002")
end
end
I think your problem is building objects with associations. We solved this problem using ignore block to lazily build associations.
FactoryGirl.define do
# User factory
factory :user do
# ...
end
# UserPost factory
factory :user_post do
# nothing in this block gets saved to DB
ignore do
user { create(:user) } # call User factory
end
post "Good day..!!"
# get id of user created earlier
user_id { user.id }
# create 2 comments for this post
comment { 2.times.collect { create(:comment) } }
end
end
# automatically creates a user for the post
FactoryGirl.create(:user_post)
# manually overrides user for the post
user = FactoriGirl.create(:user)
FactoryGirl.create(:user_post, user: user)
One fix... In the :user_post factory, you should create an array of Comment objects for UserPost.comment because of embeds_many. Not just a single one.

Mongoid: persistence in 1 to many relation within embedded document

I have a problem with relationships persisting in an embedded document. Here is the example
require 'mongoid'
require 'mongo'
class User
include Mongoid::Document
field :name
key :name
embeds_one :garage
end
class Garage
include Mongoid::Document
field :name
has_many :tools, autosave: true
has_many :cars, autosave: true
end
class Tool
include Mongoid::Document
belongs_to :garage, inverse_of: :tools
end
class Car
include Mongoid::Document
field :name
belongs_to :garage, inverse_of: :cars
end
When I run the following:
Mongoid.configure do |config|
config.master = Mongo::Connection.new.db("mydb")
end
connection = Mongo::Connection.new
connection.drop_database("mydb")
database = connection.db("mydb")
user = User.create!(name: "John")
user.build_garage
user.garage.cars << Car.create!(name: "Bessy")
user.save!
puts "user #{user}, #{user.name}"
user.garage.cars.each do |car|
puts "car is #{car}"
end
same_user = User.first(conditions: {name: user.name})
puts "same_user #{same_user}, #{same_user.name}"
same_user.garage.cars.each do |car|
puts "car found is #{car}"
end
the output is:
user #<User:0x00000003619d30>, John
car is #<Car:0x00000003573ca0>
same_user #<User:0x000000034ff760>, John
so the second time the user's car array is not outputted. How do i get the array of cars to persist?
Thanks
You need to do a user.garage.save to save the embedded garage after you populate it.

Mongoid Circular References

So I have a circular reference problem that I don't know how to solve. So the situation is the user has the ability to input a list of multiple films for actors into a text box. The text is held in a virtual attribute and parsed and given to the models in a model's save callback. Ideally I would not like to change the structure of the models because it makes english sense and seems very normal from a database standpoint.
Here's the structure:
require 'mongoid'
require 'mongo'
class Actor
include Mongoid::Document
field :name
attr_accessible :user_input_films
attr_accessor :user_input_films
before_save :assign_films
embeds_one :filmography
belongs_to :cast
def assign_films
if user_input_films == nil
return
end
user_input_films.split(" ").each do |film|
film = Film.first(conditions: { :name => film})
if film == nil
film = Film.new(:name => film)
film.build_cast
film.save!
end
self.filmography.add_film(film)
end
end
end
class Filmography
include Mongoid::Document
has_many :films
embedded_in :actor
def add_film(film)
films << film
film.cast.actors << self.actor
end
end
class Film
include Mongoid::Document
field :name
embeds_one :cast
belongs_to :filmography
end
class Cast
include Mongoid::Document
embedded_in :film
has_many :actors
end
Mongoid.configure do |config|
config.master = Mongo::Connection.new.db("mydb")
end
connection = Mongo::Connection.new
connection.drop_database("mydb")
database = connection.db("mydb")
actor = Actor.new
actor.build_filmography
actor.user_input_films = "BadFilm1 BadFilm2"
actor.save
puts "actor = #{actor.attributes}"
actor.filmography.films.each do |film|
puts "film = #{film.attributes}"
end
So the structure is: Actor owns Filmography which references many Films. And Film owns Cast which references many Actors. And the problem happens in the line:
film.cast.actors << self.actor
because the actor is then saved again through the << operator and the circular logic happens again
And as everyone guessed the error is:
/home/greg/.rvm/gems/ruby-1.9.2-p290#rails31/gems/mongoid2.2.1/lib/mongoid/fields.rb:307: stack level too deep (SystemStackError)
So how can I save my document without the circular reference stack overflow?
UPDATE:
One Thought, I think a dirty solution would be a way of adding a reference without saving the referenced object.
Thanks

Resources