Mongoid embeds_many documents are not removed after reload - ruby-on-rails

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

Related

Prevent from raising ActiveRecord::RecordInvalid or adding twice on has_many association

I want to change has_many association behaviour
considering this basic data model
class Skill < ActiveRecord::Base
has_many :users, through: :skills_users
has_many :skills_users
end
class User < ActiveRecord::Base
has_many :skills, through: :skills_users, validate: true
has_many :skills_users
end
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
validates :user, :skill, presence: true
end
For adding a new skill we can easily do that :
john = User.create(name: 'John Doe')
tidy = Skill.create(name: 'Tidy')
john.skills << tidy
but if you do this twice we obtain a duplicate skill for this user
An possibility to prevent that is to check before adding
john.skills << tidy unless john.skills.include?(tidy)
But this is quite mean...
We can as well change ActiveRecord::Associations::CollectionProxy#<< behaviour like
module InvalidModelIgnoredSilently
def <<(*records)
super(records.to_a.keep_if { |r| !!include?(r) })
end
end
ActiveRecord::Associations::CollectionProxy.send :prepend, InvalidModelIgnoredSilently
to force CollectionProxy to ignore transparently adding duplicate records.
But I'm not happy with that.
We can add a validation on extra validation on SkillsUser
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
validates :user, :skill, presence: true
validates :user, uniqueness: { scope: :skill }
end
but in this case adding twice will raise up ActiveRecord::RecordInvalid and again we have to check before adding
or make a uglier hack on CollectionProxy
module InvalidModelIgnoredSilently
def <<(*records)
super(valid_records(records))
end
private
def valid_records(records)
records.with_object([]).each do |record, _valid_records|
begin
proxy_association.dup.concat(record)
_valid_records << record
rescue ActiveRecord::RecordInvalid
end
end
end
end
ActiveRecord::Associations::CollectionProxy.send :prepend, InvalidModelIgnoredSilently
But I'm still not happy with that.
To me the ideal and maybe missing methods on CollectionProxy are :
john.skills.push(tidy)
=> false
and
john.skills.push!(tidy)
=> ActiveRecord::RecordInvalid
Any idea how I can do that nicely?
-- EDIT --
A way I found to avoid throwing Exception is throwing an Exception!
class User < ActiveRecord::Base
has_many :skills, through: :skills_users, before_add: :check_presence
has_many :skills_users
private
def check_presence(skill)
raise ActiveRecord::Rollback if skills.include?(skill)
end
end
Isn't based on validations, neither a generic solution, but can help...
Perhaps i'm not understanding the problem but here is what I'd do:
Add a constraint on the DB level to make sure the data is clean, no matter how things are implemented
Make sure that skill is not added multiple times (on the client)
Can you show me the migration that created your SkillsUser table.
the better if you show me the indexes of SkillsUser table that you have.
i usually use has_and_belongs_to_many instead of has_many - through.
try to add this migration
$ rails g migration add_id_to_skills_users id:primary_key
# change the has_many - through TO has_and_belongs_to_many
no need for validations if you have double index "skills_users".
hope it helps you.

Mongoid: embeds_one and _destroy param at embedded documents

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.

Association in Mongodb in rails many time fails to find data but sometime works

Here i have Review module
class Review
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :job
has_many :options, :class_name => "Option"
accepts_nested_attributes_for :options, allow_destroy: true
end
Also Option Model
class Option
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
field :comment, type: String
belongs_to :review,:class_name => "Review"
end
Now in my Employee:: Review controller
def show
#employee_review = Employee::Review.find(params[:id])
#employee = Employee::Employee.find(#employee_review.employee)
#employee_id = #employee.id
#job_title = #employee.job.id
#review = Review.find_by(job_id: #job_title)
end
Here #review = Review.find_by(job_id: #job_title)
This code rarely works.
I checked out there id passed in #job_title everytime But it does not find the data inside Review model everytime. But when i use Reviews controller to check data then it works automatically in Employee:: Review controller to find that data related to given job_id .
What could be the reason of not finding data in Review model through this controller?
But sometime it finds.
What happens if you go the other way around?
#review = Job.find(#job_title).reviews

How to save embedded classes in mongoid?

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

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