I am using mongoid for the first time and I am having a problem creating a has_many association in a factory for my specs.
The scenario is this:
I have the a group class:
class Group
include Mongoid::Document
field :name, :type => String
end
And I have an exercise class. An exercise can belong to many groups. The exercise class is currently defined like this:
class Exercise
include Mongoid::Document
field :name, :type => String
field :description, :type => String
has_many :groups
validates_presence_of :name, :description
end
I want to use factorygirl to create instances for specs. I am struggling with how to do this.
Currently my exercise factory looks like this;
FactoryGirl.define do
factory :exercise do
name "Preacher curls"
description "Do something"
after(:build) do |exercise|
exercise.groups << FactoryGirl.build(:group)
end
end
end
This causes the following error:
NoMethodError:
undefined method `=' for #<Group _id: 4fbc6f5a26a3181742000004, _type: nil, name: "Arms">
How can I create the exercise factory correctly to add the group_ids?
Try to add
belongs_to :exercise
into your Group class
It should look like this:
class Group
include Mongoid::Document
field :name, :type => String
belongs_to :exercise
end
Related
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.
I am using Rails 3 with mongoid 2. I have a mongoid class forum, which embeds_many topics.
Topics embeds_many forumposts
When I try to save a forumpost doing the following in my controller...
#forum = Forum.find(params[:forum_id])
#forum.topics.find(params[:topic_id]).forumposts.build(:topic_id => params[:forumpost][:topic_id], :content => params[:forumpost][:content], :user_id => current_user.id,:posted_at => Time.now, :created_at => Time.now, :updated_at => Time.now)
if #forum.save
On save I get...
undefined method `each' for 2012-11-14 23:15:39 UTC:Time
Why am I getting that error?
My forumpost class is as follows...
class Forumpost
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
field :content, type: String
field :topic_id, type: String
field :user_id, type: String
field :posted_at, type: DateTime
attr_accessible :content, :topic_id, :user_id, :posted_at, :created_at, :updated_at
validates :content, presence: true
validates :topic_id, presence: true
validates :user_id, presence: true
belongs_to :topic
belongs_to :user
end
There is alot wrong/wierd with your example code, so lets see if we can start at the start:
You say forum embeds many topics, which embeds many posts. But your model is using a belongs_to association. Belongs_to is used for references which are different than embedded documents. If your Topic model has this:
class Topic
...
embeds_many :forumposts
...
end
Then your Forumpost model should have this:
class Forumpost
...
embedded_in :topic
...
end
Read up on references vs embedded documents here: http://mongoid.org/en/mongoid/docs/relations.html
Next point, You don't need to put :topic_id into the forumpost since you are building it off the topic.
Next point, don't save the forum, save the forumpost. And instead of doing a build followed by a save, try just doing it as a create in one go.
Next point, instead of setting user_id => current_user.id, try setting user => current_user. This is the magic that the belongs_to association provides... its cleaner and avoids messing around with IDs.
Next point, why do you need both created_at (supplied by Mongoid::Timestamps) and posted_at ?
Last point, you shouldn't need to set the timestamps, they should be set automatically when created/updated (unless for some reason you actually need posted_at).
Try something more like this:
#forum = Forum.find(params[:forum_id])
#topic = #forum.topics.find(params[:topic_id])
if #topic.forumposts.create(:content => params[:forumpost][:content], :user => current_user)
#handle the success case
else
#handle the error case
end
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.
I can't seem to figure out why Mongoid won't set the nested attributes for a child object when I create a new parent. I want to create a new Folio, add one child Feature, then push it on the Folios array on Profile.
I have a Profile, which embed many Folios, which embed many Features:
class Profile
include Mongoid::Document
include Mongoid::Timestamps::Updated
#regular fields here; removed for brevity
embeds_many :folios, class_name: "Folio"
end
class Folio
include Mongoid::Document
include Mongoid::Timestamps::Updated
accepts_nested_attributes_for :features
embedded_in :profile
field :name
field :desc
field :order, type: Integer, default:0
embeds_many :features
attr_accessible :name, :desc, :order
end
class Feature
include Mongoid::Document
include Mongoid::Timestamps::Updated
embedded_in :folio
belongs_to :project
field :content_type, type: Integer #ContentType
field :content_id
field :txt, type: String
field :order, type: Integer, default:0
attr_accessible :project_id, :content_type, :content_id, :txt, :order
end
Controller:
def new
#folio = Folio.new
#folio.features.build
end
def create
#folio = Folio.new(params[:folio])
##folio.features is still empty here.
#profile.folios << #folio
#profile.save
render "create_or_update.js"
end
In create, the param hash looks good:
{"folio"=>{"id"=>"new", "name"=>"new name", "desc"=>"new description", "features_attributes"=>{"0"=>{"project_id"=>"4ea0b68e291ebb44a100000a", "content_type"=>"1", "content_id"=>"4ea0b68e291ebb44a100000d", "txt"=>"note here"}}}, "commit"=>"Save", "action"=>"create", "controller"=>"folios"}
But #folio.features is still empty.
This worked fine with AR, if I remember. Strangely, there is no features_attributes=() method on Folio. I thought that was required for the nested attributes to work? What am I missing?
This is on Rails 3.1 with Mongoid 2.2.3.
have you tried enabling AutoSave true for features in Folio document
class Folio
include Mongoid::Document
include Mongoid::Timestamps::Updated
accepts_nested_attributes_for :features , :autosave => true
embedded_in :profile
end
with a has_one/belongs_to relationship, i cannot seem to update nested records via mass assignment.
models:
class ProductVariation
include Mongoid::Document
has_one :shipping_profile, :inverse_of => :variation
field :quantity
attr_accessible :shipping_profile_attributes
accepts_nested_attributes_for :shipping_profile
end
class ShippingProfile
include Mongoid::Document
belongs_to :variation, :class_name => "ProductVariation"
field :weight, :type => Float
attr_accessible :weight
end
controller:
#variation = ProductVariation.find(params[:id])
#variation.update_attributes(params[:product_variation])
post request:
Parameters:{
"product_variation"=>{
"quantity"=>"13",
"shipping_profile_attributes"=>{
"weight"=>"66",
"id"=>"4dae758ce1607c1d18000074"
}
},
"id"=>"4dae758ce1607c1d18000073"
}
mongo query:
MONGODB app_development['product_variations'].update({"_id"=>BSON::ObjectId('4dae758ce1607c1d18000073')}, {"$set"=>{"quantity"=>13, "updated_at"=>2011-04-28 06:59:17 UTC}})
and i dont even get a mongo update query if the product_variation doesnt have any changed attributes... what am i missing here?
Working models and a unit test are below to demonstrate that you can update child parameters and save to the database
through the parent as intended via accepts_nested_attributes_for and the autosave: true option for relations.
The Mongoid documentation says that an error will be raised for an attempt to set a protected field via mass assignment,
but this is out of date.
Instead, messages like the following are printed to the log file.
WARNING: Can't mass-assign protected attributes: id
You should carefully look for for these messages in the appropriate log file to diagnose your problem.
This will help you notice that you have a nested id field for the shipping profile in your parameters,
and this seems to cause the weight to be rejected as well, probably along with all child parameters.
After adding "attr_accessible :id" to the ShippingProfile model, the weight now gets assigned.
You also need to add "attr_accessible :quantity" (and I've added :id for the unit test) to the ProductVariation model
The next issue is that you need "autosave: true" appended to the has_one relation
in order to have the child updated through the parent,
otherwise you will have to save the child manually.
You might also be interested in sanitize_for_mass_assignment, which can be used to launder out ids.
include ActiveModel::MassAssignmentSecurity
p sanitize_for_mass_assignment(params['product_variation'], :default)
The unit test should make the whole subject clear, I'll leave the controller work to you.
Hope that this is clear and that it helps.
class ProductVariation
include Mongoid::Document
has_one :shipping_profile, :inverse_of => :variation, autosave: true
field :quantity
accepts_nested_attributes_for :shipping_profile
attr_accessible :id
attr_accessible :quantity
attr_accessible :shipping_profile_attributes
end
class ShippingProfile
include Mongoid::Document
belongs_to :variation, :class_name => "ProductVariation"
field :weight, :type => Float
attr_accessible :id
attr_accessible :weight
end
test/unit/product_varitation_test.rb
require 'test_helper'
class ProductVariationTest < ActiveSupport::TestCase
def setup
ProductVariation.delete_all
ShippingProfile.delete_all
end
test "mass assignment" do
params = {
"product_variation"=>{
"quantity"=>"13",
"shipping_profile_attributes"=>{
"weight"=>"66",
"id"=>"4dae758ce1607c1d18000074"
}
},
"id"=>"4dae758ce1607c1d18000073"
}
product_variation_id = params['id']
shipping_profile_id = params['product_variation']['shipping_profile_attributes']['id']
product_variation = ProductVariation.create("id" => product_variation_id)
shipping_profile = ShippingProfile.create("id" => shipping_profile_id)
product_variation.shipping_profile = shipping_profile
assert_equal(1, ProductVariation.count)
assert_equal(1, ShippingProfile.count)
product_variation.update_attributes(params['product_variation'])
assert_equal('13', ProductVariation.find(product_variation_id)['quantity'])
assert_equal(66.0, ShippingProfile.find(shipping_profile_id)['weight'])
p ProductVariation.find(product_variation_id)
p ShippingProfile.find(shipping_profile_id)
end
end
test output
Run options: --name=test_mass_assignment
# Running tests:
#<ProductVariation _id: 4dae758ce1607c1d18000073, _type: nil, quantity: "13">
#<ShippingProfile _id: 4dae758ce1607c1d18000074, _type: nil, variation_id: BSON::ObjectId('4dae758ce1607c1d18000073'), weight: 66.0>
.
Finished tests in 0.014682s, 68.1106 tests/s, 272.4424 assertions/s.
1 tests, 4 assertions, 0 failures, 0 errors, 0 skips
Process finished with exit code 0