Mongoid: embeds_one and _destroy param at embedded documents - ruby-on-rails

I have such model.
class Article
include Mongoid::Document
embeds_many :blocks, class_name: 'Article::Block', cascade_callbacks: true
accepts_nested_attributes_for :blocks, allow_destroy: true
...
class Block
include Mongoid::Document
embedded_in :article
embeds_one :squib, class_name:'Article::Block::Squib', cascade_callbacks: true
accepts_nested_attributes_for :squib, allow_destroy: true
...
class Squib
include Mongoid::Document
...
embedded_in :block, class_name: 'Article::Block'
end
end
end
problem is about firing callbacks. When I pass to controller following params:
{"article"=>{"_id"=>"55d4c8a43a98c118b100001a", ... , "blocks_attributes"=>[{... "squib_attributes"=>{... "_destroy"=>1, "_id"=>"55d4ccb63a98c118b1000044"}, "_id"=>"55d4c8d73a98c118b100001c"}]}}
Embedded Article::Block::Squib doesn't destroys. There is no problem when I am using embeds_many relation. Problem only in embeds_one.
How to fix it?
Mongoid version 4.0.2

I found solution at mongoid sources. There is code with check for embeds_one relation:
def delete?
destroyable? && !attributes[:id].nil?
end
mongoid-4.0.2/lib/mongoid/relations/builders/nested_attributes/one.rb:82
It means that embeds_one understands only :id doc identifier,instead of embeds_many, that allow you to pass embedded documents with :_id doc identifier.
Instead of:
{"article"=>{"_id"=>"55d4c8a43a98c118b100001a", ... ,
"blocks_attributes"=>[{... "squib_attributes"=>{... "_destroy"=>1,
"_id"=>"55d4ccb63a98c118b1000044"},
"_id"=>"55d4c8d73a98c118b100001c"}]}}
you should pass
{"article"=>{"_id"=>"55d4c8a43a98c118b100001a", ... ,
"blocks_attributes"=>[{... "squib_attributes"=>{... "_destroy"=>1,
"id"=>"55d4ccb63a98c118b1000044"},
"_id"=>"55d4c8d73a98c118b100001c"}]}}
to update_attributes method.

Related

Rails complicated selection with order in mongoid

I have:
gem 'rails', '~> 5.1.1'
gem 'mongoid', '~> 6.1.0'
I have 3 models, related to this question.
class Book
include Mongoid::Document
field :name, type: String
field :author, type: String
field :description, type: String
field :status, type: String
field :image, type: String
has_many :histories, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :comments, dependent: :destroy
def ordered_histories
histories.order(taken_in: :desc)
end
def book_rating
rating_array = likes.map { |like| like.rate.to_i }
return 0 if rating_array.empty?
(rating_array.sum / rating_array.size).round
end
end
class Like
include Mongoid::Document
field :rate, type: Integer
belongs_to :book
belongs_to :user
end
class History
include Mongoid::Document
field :taken_in, type: DateTime
field :returned_in, type: DateTime
belongs_to :book
belongs_to :user
end
I need to get top 5 books which have the most count of likes and histories.
As I know - mongoid not support joins.
I thought about storeing likes_count and histories_count in Book model. But I think that it is not best way. Please help me to find better solution to get top 5 books from mongodb.
You should use the aggregation framework.
Try the following code to get the ids of the top 5 books with most histories count. The same approach can be used for the Like class.
History.collection.aggregate([
{"$group" => {
"_id" => "$book_id",
"histories" => {"$sum" => 1}
}},
{"$sort" => { "histories" => -1}},
{"$limit" => 5 }
])
I found another way to make it works.
I have add counter_cash to model Like and History:
class Like
include Mongoid::Document
field :rate, type: Integer
belongs_to :book, counter_cache: :likes_count
belongs_to :user
end
class History
include Mongoid::Document
field :taken_in, type: DateTime
field :returned_in, type: DateTime
belongs_to :book, counter_cache: :histories_count
belongs_to :user
end
After that I use one query to get top 5 books which have the most count of likes and histories:
#top_books = Book.all.order_by(likes_count: :desc, histories_count: :desc).limit(5)
I think it is one of possible way to do this. May be it help somebody. Tell me please, if I made any mistakes.

Mongoid Association Creating (unwanted) Records

I'm at a loss to why Mongoid is creating a new record in an association. I'm stepping closely through the code, but I've never seen anything like this. I've made a test and slimmed down the code. I left the VCR in just in case it might be related.
it "should not create a duplicate entry for MT" do
state = PolcoGroup.create(type: :state, name: 'MT', active: true)
s = state.get_senators
state.junior_senator = s[:state_junior_senator] # !!!!! this creates a new record
state.senior_senator = s[:state_senior_senator] # !!!!! so does this line
expect(Legislator.all.size).to eql(2) # actually equals 4 -- each association creates a new record
end
result is:
Legislator.all.map(&:sortname)
=> ["Tester, Jon (Sen.) [D-MT]", "Walsh, John (Sen.) [D-MT]", "Walsh, John (Sen.) [D-MT]", "Tester, Jon (Sen.) [D-MT]"]
## models
class PolcoGroup
include Mongoid::Document
include Mongoid::Timestamps
include VotingMethods
include DistrictMethods
extend DistrictClassMethods
include StateMethods
field :name, :type => String
...
# STATE RELATIONSHIPS -----------------------------
has_one :junior_senator, class_name: "Legislator", inverse_of: :jr_legislator_state
has_one :senior_senator, class_name: "Legislator", inverse_of: :sr_legislator_state
...
end
class Legislator
include Mongoid::Document
include Mongoid::Timestamps
# the following fields are directly from govtrack
field :govtrack_id, type: Integer
field :bioguideid, type: String
...
belongs_to :jr_legislator_state, class_name: "PolcoGroup", inverse_of: :junior_senator
belongs_to :sr_legislator_state, class_name: "PolcoGroup", inverse_of: :senior_senator
...
end
module StateMethods
def get_senators
...
# just returns the following
{state_senior_senator: senators.first, state_junior_senator: senators.last}
end
end
You can see more code here: https://gist.github.com/tbbooher/d892f5c234053990da70
OK -- never do what I did. I was pulling in an old version of mongo as a test database and then conducting the above. Of course it wasn't working correctly.

Mongoid embeds_many documents are not removed after reload

I have the following two models:
class Customer
include Mongoid::Document
include Mongoid::Timestamps
embeds_many :locks, class_name: "Lock"
accepts_nested_attributes_for :locks, allow_destroy: true
field :name, type: String
validates :name,
presence: true
belongs_to :list
end
and
class Lock
include Mongoid::Document
include Mongoid::Timestamps
field :locked_by, type: Moped::BSON::ObjectId
embedded_in :customer, inverse_of: :locks, class_name: "Customer"
def unlock!
self.destroy
end
end
So when I try to delete an lock the lock is removed from the child collection but after reload of customer it is still there:
locks = customer.locks.where({ some conditions})
locks.each do |l|
l.unlock!
end
customer.save
The where conditions definitely returns the correct objects.
Can somebody help me and tell me what I did wrong?
Update:
This does not work also
customer.locks = []
customer.save
customer.reload
Well, lets try.
First, delete this block
def unlock!
self.destroy
end
Then, replace
locks = customer.locks.where({ some conditions})
locks.each do |l|
l.unlock!
end
with
customer.locks.where({ some conditions}).delete_all
If still it does not work, please add this one more line after the line above
customer.locks.save

Changing Mongoid class name mid-production

Is this even possible?
I have a mongoid class named Magazine, with some associations as well, that I would like to re-name to Publication. Problem is that I already have a bunch of users who have already made magazines, issues and articles.
Original Magazine model:
class Magazine
# 1. Include mongoid stuff
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Slug
# 2. Define fields
field :title, type: String
field :description, type: String
field :live, type: Boolean, default: false
field :show_walkthrough, type: Boolean, default: true
# 3. Set attributes accesible
attr_accessible :title, :description, :live, :show_walkthrough, :cover_image_attributes, :logo_image_attributes
# 4. Set slug
slug :title
# 5. Set associations
belongs_to :user
has_many :issues, dependent: :delete, autosave: true
has_one :foreword, :as => :articleable, :class_name => 'Article', dependent: :delete, autosave: true
embeds_one :cover_image, :as => :imageable, :class_name => 'Image', cascade_callbacks: true, autobuild: true
embeds_one :logo_image, :as => :imageable, :class_name => 'Image', cascade_callbacks: true, autobuild: true
# 6. Accepting nested attributes
accepts_nested_attributes_for :cover_image, :allow_destroy => true
accepts_nested_attributes_for :logo_image, :allow_destroy => true
# 7. Set validations
validates_presence_of :title, :description, :cover_image, :logo_image
end
I know I can change the class-name to Publication and then do db.magazines.renameCollection( "publications" ) on the mongodb, but the associations doesn't follow along.
Any suggestions?
I looks like you have association fields in your Issue and Foreword models that probably refer to Magazine. So if you are happy enough to change the name of the class and underlying collection then renaming these association fields is your main problem. You may have something like:
class Issue
belongs_to :magazine
end
You could redefine this association as belongs_to :publication. Assuming that are happy to fix all the references to Issue#magazine in your code then your remaining problem is that your issues collection will be full of documents that have a magazine_id field instead of publication_field. You have two options to fix the database mapping.
First option is to rename the field in the database. See mongoDB : renaming column name in collection
The second option is to declare the association so that it maps to the old database field by overriding the 'foreign key' name:
belongs_to :publication, foreign_key: :magazine_id
You will have to repeat this for the Foreword model and any others that reference Magazine.
Just a heads up for polymorphism and class inheritance.
Mongoid handles inheritance and polymorphic associations by storing the class name as a document attribute.
On the class itself, this is stored as the "_type" attribute
For polymorphic associations like belongs_to :polymorphic_class mongoid adds an attribute "polymorphic_class_type", so that the class can be resolved (with Rails' .constantize) when browsing polymorphic associations.
So if you decide to change the class name, and you have inheritance or polymorphic associations, well you'll have to also rewrite all those attributes !

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

Resources