Search code examples
ruby-on-rails-3mongodbpersistencemongoidembedded-database

saving embeds_many in mongoid


I have a embeds_many association and when I save the parent document the children are not being saved.

class User
  include Mongoid::Document

  embeds_many :user_missions
  attr_accessible :user_missions_attributes
  accepts_nested_attributes_for :user_missions, allow_destroy: true
end

class UserMission
  include Mongoid::Document

  embedded_in :user, :inverse_of => :user_missions

  has_one :mission, autosave: true
  validates_presence_of :mission

  attr_accessible :mission_title

  def mission_title
    mission.try(:title)
  end

  def mission_title=(title)
    self.mission = Mission.find_or_create_by(:title => title) if title.present?
  end
end

Here is the spec I'm failing:

it "should save mission to user_mission when created" do
  user_mission = UserMission.new
  user = create(:user)
  user.user_missions << user_mission
  user_mission.mission_title = "Created Title"

  user.save!
  #user_mission.save!

  User.first.user_missions[0].mission.title.should == "Created Title"
end

I get:

undefined method `title' for nil:NilClass

When I comment in the line #user_mission.save! it works. The problem is I need this to work in a form and I thought Mongoid automatically saved embedded document's fields.

How do I get the parent User document to save the embedded UserMission's data?

Thanks

UPDATE

Here is the Mission model I forgot to add (wasn't sure if it was important):

class Mission
  include Mongoid::Document

  belongs_to :user_mission, :inverse_of => :mission

  attr_accessible :title
  field :title, type: String
  validates_presence_of :title

  field :lowercase_title
  before_create :lower_title_case

  field :description, type: String

private
  def lower_title_case
    self.lowercase_title = self.title.downcase
  end
end

Solution

  • AFAIK, embedded documents can't have referenced relations. So the calls to embedded_in and has_one are contradictory.

    Other than that, have you tried reordering the lines in your test so that the user gets created first, and then you create user_misison through the user?

    user = create(:user)
    user_mission = user.user_missions.new
    user_mission.mission_title = "Created Title"
    
    user.save!
    

    It looks like what you're trying to do is similar to an SQL JOIN table. If this is what you're trying to achieve, it would be better to take advantage of Mongoid's awesome N-N referenced mapping (unless you need to store extra data in the 'join' collection). I'd do something like:

    class User
      include Mongoid::Document
      has_and_belongs_to_many :missions
    end
    
    class Mission
      include Mongoid::Document
      has_and_belongs_to_many :users
    end
    

    If you want to create missions through users, then turn on autosave on the Mission side of the relation.

    EDIT: After seeing your Mission model, I realize you're trying to directly reference UserMission (belongs_to :user_mission), an embedded document, directly from your Mission model. Like I said earlier, not doable. You need some relation between the top level docs, User and Mission. You could probably get things to work by changing that line to:

    has_many :users
    

    and then changing has_one :mission from the User model to:

    belongs_to :mission
    

    You won't be able to use autosave from the User side though.

    EDIT: Corrected for proper way to show right way to do 1-n relation from users to missions.