How to load Factories for Embedded Models (Mongoid - Rails) - ruby-on-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.

Related

Build factories for self referential associations in rails

I have a typical requirement, I have to address user object as follows
user.referrer and user.referrers.
Basically, user can refer more than one person and one person should be referred by one particular user. So I build associations as follows. They are working great.
class User < ActiveRecord::Base
attr_accessible :account_number, :display_name, :referrer_id
has_many :referrers, :class_name => "User", :foreign_key => "referrer_id"
belongs_to :referrer, :class_name => "User"
end
Now I would like to test assoications in Rspec. I am using factory girl so any one help me to build factories.
I tried as follows but end up with an errors
factory :user do
gender :male
name "super test"
.....
.....
factory :referrer do
end
association :referrer
end
You need to build two factories here, one for user with a referrer and second one for user without a referer - otherwise you'll end up in the infinite creation loop. You might use traits for this:
factory :user do
gender :male
name "super test"
trait :with_referrer do
association :referrer, factory: :user
end
end
FactoryGirl.create(:user, :with_referrer)

Factory girl : validates associated leads to undefined method valid? for nil:Nilclass

I have model Student, which has_one :account.
All input-related data is stored inside of account. Student model just plays it's role when it comes to relations (some other models belong to student).
Problem: I can't test it with factory girl.
factory :student do
end
As I can't define anything besides it.
What I get on every attempt of #student = FactoryGirl.create(:student):
undefined method `valid?' for nil:NilClass
Any fixes?
Additional code
class Account < ActiveRecord::Base
belongs_to :account_holder, :polymorphic => true
...
end
factory :account do
sequence :name do |n|
"Name#{n}"
end
sequence :surname do |n|
"Surname#{n}"
end
sequence :phone do |n|
"8911222332#{n}"
end
sequence :email do |n|
"somemail#{n}#mail.ru"
end
student
end
Source of issue
Student has:
validates_associated_extended :account
which is basically usual validate but with error extraction for parent model.
So when FactoryGirl attempts to create student, it validates account, which is nil.
I tried this:
before(:create) {|student| build(:account, :account_holder =>student )}
in student factory, while in account factory:
association :account_holder, :factory=>:student
But it still doesn't work.
factory :student do
after :build do |student|
student.account << create(:account, :account_holder => student) if student.account_holder.nil?
end
end
This allows you to have both a valid student and to specify an account if you want to force one.
I think latest versions of FactoryGirl even allow that to be written as lazy attribute syntax:
factory :student do
account { create(:account) }
end

FactoryGirl define attribute by calling method on another factory

Here is an example from the FactoryGirl documentation:
FactoryGirl.define do
factory :post do
name "Post name"
user
end
end
In this example, user is invoking another factory. What I would like to do is effectively call user.id, but to set it as the definition of an attribute. Here's a stripped-down example:
**models/job.rb**
...
belongs_to :assignee, :class_name => "User"
belongs_to :user
...
attr_accessible :assignee_id, :user_id
...
end
**factories/jobs.rb**
FactoryGirl.define do
factory :job do
assignee_id user.id #what I would like to do, but triggers "undefined method 'id'" error
user_id user.id #user_id is an attribute of the model and is the job assignor
end
I've tried to incorporate the part of the documentation that discusses aliases, but with no luck:
FactoryGirl.define do
factory :user, :aliases => [:assignee] do
....
I feel like (hope?) I'm close here, but any insight is appreciated. Thanks.
EDIT: This code gets my specs running!
**factories/jobs.rb**
FactoryGirl.define do
factory :job do
before(:create) do |job|
user = FactoryGirl.create(:user)
job.assignee = user
job.user = user
end
association :assignee, factory: :user
association :user, factory: :user
sequence(:user_id) { |n| n }
sequence(:assignee_id) { |n| n }
...
end
And it passes my it { should be_valid } spec, so it seems that the factory is fine, though I think I have some refactoring in the spec itself when I'm calling FactoryGirl.create.
The code above incorporates the suggestions from mguymon. Thanks!
FINAL UPDATE
After going back and re-reading Hartl's discussion on model associations, I was able to put this matter to rest. What I have above was techincally valid, but didn't actually pass the attributes in properly when i built or created jobs in my spec. Here's what I should have had:
FactoryGirl.define do
factory :job do
association :assignee, factory: :user
user
end
end
My problem also stemmed from how I was creating factories in my spec, so here's how I should have been doing it (but wasn't...sigh):
let(:user) { create(:user) }
before { #job = create(:job, user: #user) }
It seems that I don't explicitly have to have association :user in my factory, nor do I need the before block from above.
As an aside, I also learned that I can debug by including puts #job within an expect statement, or call #job.assignee_id to make sure that the attributes are being loaded properly. When that particular spec is run, the puts statement will output right by the F or . from the spec.
For the latest version of FactoryGirl, use association to map to other ActiveRecord Models:
factory :job do
# ...
association :assignee, factory: :user
end
This is straight from the docs.
From the error you posted, it is stating you are trying to get the user.id but user is not an ActiveRecord instance but a proxy from FactoryGirl. You will not get this error if you are using the association method. If you need to directly access a model, you have to manually build it first. You can do this by passing a block in your factory:
factory :job do
assignee_id { FactoryGirl.create(:user).id }
end
It looks like you are trying to associate the same model twice, for this you can use before_create callback to create a User model and assign to user and assignee:
factory :job do
before(:create) do |job|
user = FactoryGirl.create(:user)
job.assignee = user
job.user = user
end
end

Rails: Set creator of Entry

Currently, I can add the creator_id like this in my controller:
#entry = Entry.new(params[:entry].merge(:creator => current_user._id))
If this is my model:
class Entry
include Mongoid::Document
belongs_to :User
field :creator, :type => String
field :title, :type => String
field :content, :type => String
field :scorea, :type => Integer
field :scoreb, :type => Integer
field :scorec, :type => Integer
end
Is there a better way to do this?
Your model doesn't looks very good, do you really want to store the user_id in a string field?
I suggest you change your models to following:
class Entry
include Mongoid::Document
belongs_to :creator, :class_name => 'User', :inverse_of => :entries
# field definitions
end
class User
include Mongoid::Document
has_many :entries, :inverse_of => :creator
end
Once you change the models you can continue using what you are now or alternatively:
#entry = current_user.entries.build(params[:entry])
Update:
The method to initialize entry is not much different in the way I did it. It is just more towards the rails way of doing things. The main difference is that you were not using the associations. From your model definitions it is clear that you want a one-to-many association between user and entries and this is how you create such associations. Associations has a lot of goodies attached to them, like you can do following things:
user.entries << entry # add a entry to users, will automatically change entry.creator_id
entry.creator = user # sets creato_id = user_id
entry.creator # returns associated user. no need to do User.find(entry.creator_id)
user.entries # returns all entries for use <=> Entry.where(creator_id: user.id)
for more details go to http://mongoid.org/docs/relations.html
There are no better way to do. Or you can do it in 2 line :
#entry = Entry.new(params[:entry])
#entry.creator = current_user._id
this second case allow you to add creator field in attr_protected
You can do it in this manner
#entery = Entery.new(params[:entry])
#entery.creator = current_user
or
#entery.creator_id = current_user.id
hence you can assigned id of creator to this entery.

Factory Girl + Mongoid embedded documents in fixtures

Let’s say you have the following mongoid documents:
class User
include Mongoid::Document
embeds_one :name
end
class UserName
include Mongoid::Document
field :first
field :last_initial
embedded_in :user
end
How do you create a factory girl factory which initializes the embedded first name and last initial? Also how would you do it with an embeds_many relationship?
I was also looking for this one and as I was researching I've stumbled on a lot of code and did pieced them all together (I wish there were better documents though) but here's my part of the code. Address is a 1..1 relationship and Phones is a 1..n relationship to events.
factory :event do
title 'Example Event'
address { FactoryGirl.build(:address) }
phones { [FactoryGirl.build(:phone1), FactoryGirl.build(:phone2)] }
end
factory :address do
place 'foobar tower'
street 'foobar st.'
city 'foobar city'
end
factory :phone1, :class => :phone do
code '432'
number '1234567890'
end
factory :phone2, :class => :phone do
code '432'
number '0987654321'
end
(And sorry if I can't provide my links, they were kinda messed up)
Here is a solution that allows you to dynamically define the number of embedded objects:
FactoryGirl.define do
factory :profile do
name 'John Doe'
email 'john#bigcorp.com'
user
factory :profile_with_notes do
ignore do
notes_count 2
end
after(:build) do |profile, evaluator|
evaluator.notes_count.times do
profile.notes.build(FactoryGirl.attributes_for(:note))
end
end
end
end
end
This allows you to call FactoryGirl.create(:profile_with_notes) and get two embedded notes, or call FactoryGirl.create(:profile_with_notes, notes_count: 5) and get five embedded notes.

Resources