single table inheritance with embeds_one mogoid - ruby-on-rails

I have a model
class Post
include Mongoid::Document
include Mongoid::Timestamps
embeds_one :comment
end
and I have comment class
class Comment
include Mongoid::Document
include Mongoid::Timestamps
embedded_in :post
field :title
field :description
end
And I have another class inherited from comment
class RecentComment < Comment
# certain methods
end
Now I want to be able to create RecentComment through post if I do Post.last.build_comment(:_type => "RecentComment") the new comment will not be of _type:"RecentComment", and similarly if I do Post.last.build_recent_comment, it gives me error saying sth like undefined method build_recent_comment for Post class. If the post had references_many :comments I should have done Post.last.build_comments({}, RecentComment) without any problems. But I don't know about how to build an object with RecentComment class in this case. If anybody could help that'd be gr8!
Note: I am using gem 'mongoid', '~> 2.0.1'

Maybe try
class Post
include Mongoid::Document
include Mongoid::Timestamps
embeds_one :recent_comment, :class_name => Comment
and just make your Comment class polymorphic
class Comment
include Mongoid::Document
include Mongoid::Timestamps
field :type
validates_inclusion_of :type, :in => ["recent", "other"]

one option is to try something like:
class RecentComment < Comment
store_in "comment"
#set the type you want
end
but you might just use timestamps
and scope to retrieve your recent,
old comment, new_comment and such,
like within the comment class
scope :recent, where("created_at > (Time.now - 1.day)")
then you can do:
post.comments.recent

Related

Mongoid Polymorphic Association Rails

Work env: Rails 4.2 mongoid 5.1
Below are my models:
class Tag
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
belongs_to :entity_tags, :polymorphic => true
end
class EntityTag
include Mongoid::Document
include Mongoid::Timestamps
field :tag_id, type: String
field :entity_id, type: String // Entity could be Look or Article
field :entity_type, type: String // Entity could be Look or Article
field :score, type: Float
end
class Look
include Mongoid::Document
include Mongoid::Timestamps
has_many :tags, :as => :entity_tags
end
class Article
include Mongoid::Document
include Mongoid::Timestamps
has_many :tags, :as => :entity_tags
end
We are trying to implement polymorphic functionality between Looks and Articles to Tags.
i.e. Let's say we have a Tag named "politics", and we would like to add the tag to an Article with the score '0.9' and to a Look with the score '0.6'. The Score should be saved at the EntityTags Model.
The problem:
The first assign of the tag works, but then when I try to assign the same tag to another entity, it removes it and reassigns it from the first one to the latter.
The assignment looks like the following:
entity.tags << tag
Does anybody know the proper way to save associations and create the EntityTag Object with the correct polymorphism and assignment properly?
Thanks!
I've managed to implement a non-elegant working solution based on the following answer in this link

Unusual Rails Mongoid case

Previously we had has_one and belongs_to relation with our models:
class Task
include Mongoid::Document
include Mongoid::Timestamps
has_one :output
end
class Output
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :task
end
But we now plan to embed output inside task.
class Task
include Mongoid::Document
include Mongoid::Timestamps
embeds_one :output
end
class Output
include Mongoid::Document
include Mongoid::Timestamps
embedded_in :task
end
Everything works fine but we want to make backward compatible. ie. we want those output also which were created before embed.
Then, we did this method in task.rb:
def output
Task.collection.find(_id: Moped::BSON::ObjectId(self.id)).first.output || Output.collection.find(task_id: Moped::BSON::ObjectId(self.id)).first
end
The problem with this is now task.output will give json instead of output object.
so we cannot do
task = Task.new
output = task.create_output
output.task #=> not possible
Anyone having this scenario or any directions for this case.
Well, instead of making a workaround, why dont we migrate the old ones?
First, change both models to embed by replacing has_one with embeds_one and replacing belongs_to with embedded_in. Save the code.
Then use your rails console (>> rails console)
Then
Output.each do |o|
if !o.task_id.nil?
#change to embedded format
t=Task.find(o.task_id)
t.output=o
t.output.task_id=nil
t.save
end
end

rails 3 mongoid and countries gem

I'd like to use the countries gem I found here instead creating a separate model.
It works fine to inherit from but I'd also like to be able to have other classes belong_to it.
Is this possible? IE something like below. Is there some method I could use to provide a key to child classes?
https://github.com/hexorx/countries
class Country < ISO3166::Country
#include Mongoid::Document
#RELATIONS
has_many :cities
has_many :reviews, as: :reviewable
end
At the moment I get NoMethodError: undefined method `has_many' for Country:Class
Or some way to include/inherit the attributes from the gem after the object is initialized?
class Country# < ISO3166::Country
include Mongoid::Document
#field :name, :type => String
field :country_id, :type => String
##RELATIONS
has_many :cities
has_many :reviews, as: :reviewable
def after_initialize
ISO3166::Country.find_country_by_alpha3(self.country_id)
end
end
To me the best behavior is not using has_many in your case but create method you want with Mongoid query inside.

Why doesn't mongoid add my nested attributes on new()?

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

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
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.
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.
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.

Resources