Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

embeds_many and embeds_one from same model with Mongoid

I have two models, Blog and Theme. A Blog embeds_many :themes and Theme embedded_in :blog. I also have Blog embeds_one :theme (for the activated theme). This does not work. When creating a theme with blog.themes.create it's not stored. If I change the collections so they're not embedded everything works.

# This does NOT work!
class Blog
  embeds_many :themes
  embeds_one  :theme
end

class Theme
  embedded_in :blog
end

BUT

# This DOES work!
class Blog
  has_many :themes
  has_one  :theme
end

class Theme
  belongs_to :blog
end

Anyone know why this is?

UPDATE

Also there is a problem with assigning one of themes to (selected) theme.

blog.themes = [theme_1, theme_2]
blog.save!

blog.theme = blog.themes.first
blog.save!

blog.reload
blog.theme # returns nil
like image 961
sandelius Avatar asked Oct 16 '11 09:10

sandelius


3 Answers

With this approach you'll embed the same document twice: once in the themes collection and then in the selected theme.

I'd recommend removing the second relationship and use a string attribute to store the current theme name. You can do something like:

class Blog
  include Mongoid::Document
  field :current_theme_name, type: String

  embeds_many :themes

  def current_theme
    themes.find_by(name: current_theme_name)
  end
end

class Theme
  include Mongoid::Document
  field :name, type: String

  embedded_in :blog
end

Note that mongoid embeded documents are initialized at the same time that the main document and doesn't require extra queries.

like image 63
fjuan Avatar answered Oct 19 '22 23:10

fjuan


OK, so I had the same problem and think I have just stumbled across the solution (I was checking out the code for the Metadata on relations).

Try this:

class Blog
  embeds_many :themes, :as => :themes_collection, :class_name => "Theme"
  embeds_one  :theme, :as => :theme_item, :class_name => "Theme"
end

class Theme
  embedded_in :themes_collection,     :polymorphic => true
  embedded_in :theme_item,     :polymorphic => true
end

What I have discerned guessed is that:

  • the first param (e.g. :themes) actually becomes the method name.
  • :as forges the actual relationship, hence the need for them to match in both classes.
  • :class_name seems pretty obvious, the class used to actually serialise the data.

Hope this helps - I am obviously not an expert on the inner workings on mongoid, but this should be enough to get you running. My tests are now green and the data is serialising as expected.

like image 24
Rob Cooper Avatar answered Oct 19 '22 21:10

Rob Cooper


Remove embeds_one :theme and instead put its getter and setter methods in Blog class:

def theme
  themes.where(active: true).first
end

def theme=(thm)
  theme.set(active: false)
  thm.set(active: true)
end

There is no need to call blog.save! after blog.theme = blog.themes.first because set performs an atomic operation.

Also, don't forget to add field :active, type: Boolean, default: false in your Theme model.

Hope this works with you.

like image 1
Zakwan Avatar answered Oct 19 '22 21:10

Zakwan