How to validate count of related documents? - ruby-on-rails

I have two models Item and Tag
class Item
include Mongoid::Document
field :title, type: String
has_many :tags
validates_length_of :tags, minimum: 1
end
class Tag
include Mongoid::Document
field :title, type: String
belongs_to :item
end
The Item must have minimum 1 tag.
When item is created validation works very well:
item = Item.create(title: "black hole")
item.tags << Tag.create(title: "black")
item.tags << Tag.create(title: "heavy")
puts item.valid? # => true
item.save
But validation fails when the existed item is modified:
item = Item.find(item.id)
item.title = "nothing"
puts item.tags.count # => 2, it's ok
puts item.valid? # => false, it's wrong
How to validate count of related documents properly?

Have you tried adding attr_accessible to title?
It would look like this:
class Item
include Mongoid::Document
attr_accessible :title # <-- here
field :title, type: String
has_many :tags
validates_length_of :tags, minimum: 1
end

Related

rails validate uniqueness within inheritance

I have to models: Tag and TagNumeric each one with a category field
I shouldn't be able to create Tags of different types with the same category. How can I validate this?
EDIT:
I forgot to mention
TagNumeric < Tag
class Tag
include Mongoid::Document
validates_presence_of :type, :value
field :category, type: String
field :value, type: String
field :color, type: String
validates :value, :presence => true, :uniqueness => {:scope => :category}
class TagNumeric < Tag
field :value, type: Integer
it 'its category should be unique within the Tag class type' do
Tag.create(category: 'Movie', value: 'Avatar')
TagNumeric.create(category: 'Movie', value: 'Iron man').should_not be_valid
end
If anybody is having the same problem here is how I solved it
validate :uniqueness_within_category
def uniqueness_within_category
first_category_type = (Tag.find_by(category: self.category).nil?)? 'none' : Tag.find_by(category: self.category)._type
if first_category_type == 'none'
#It doesnt exist then should be allowed
return
end
#If it exists within another class type then shouldnt be allowed
if self._type != first_category_type
errors.add(:category, 'Must be unique within tag type')
end
end
Testing started at 6:22 PM ...
1 examples, 0 failures, 1 passed

Saving records recursively with Mongoid, Ancestry

I have a model Line Items embedded in Line model. In Line create view, I have provided ability to define multiple nested levels of line items.
Here is a random snap of param[:line]:
=> {"title"=>"Hello", "type"=>"World", "line_items"=>{"1"=>{"name"=>"A",
"position"=>"1", "children"=>{"1"=>{"name"=>"A1", "position"=>"1",
"children"=>{"1"=>{"name"=> "A11", "position"=>"1"}, "2"=>{"name"=>"A12",
"position"=>"2"}}}, "2"=>{"name"=>"A2", "position"=>"2"}}}, "3"=>
{"name"=>"B", "position"=>"3"}}}
In Line#create, I have:
def create
#line = Line.new(params[:line])
if #line.save
save_lines(params[:line][:line_items])
flash[:success] = "Line was successfully created."
redirect_to line_path
else
render :action => "new"
end
end
In Line#save_lines, I have:
# Save children up to fairly infinite nested levels.. as much as it takes!
def save_lines(parent)
unless parent.blank?
parent.each do |i, values|
new_root = #line.line_items.create(values)
unless new_root[:children].blank?
new_root[:children].each do |child|
save_lines(new_root.children.create(child))
end
end
end
end
end
LineItem Model looks like:
class LineItem
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Ancestry
has_ancestry
# Fields
field :name, type: String,
field :type, type: String
field :position, type: Integer
field :parent_id, type: Moped::BSON::ObjectId
attr_accessible :name, :type, :url, :position, :parent_id
# Associations
embedded_in :line, :inverse_of => :line_items
end
in Line model, i have:
# Associations
embeds_many :line_items, cascade_callbacks: true
Which work as expected. But, is there a better way to save the line_items recursively with Ancestry?
I think your code looks fine. I just refactored it.
How about:
def save_lines(parent)
parent.each do |i, values|
#get children hash if any
children = values.delete("children")
# create the object with whatever remain in values hash
#line.line_items.create(values)
# recurse if children isn't empty
save_lines(children) if children
end
end

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

Mongoid references_many ids array population

I have a question regarding mongoid id storage for references_many.
Suppose I have the following classes:
class A
include Mongoid::Document
field :name
attr_accessible :name, :b_ids
references_many :bs, :stored_as :array, :inverse_of :a
end
class B
include Mongoid::Document
field :name
attr_accessible :name, :a_id
referenced_in :a
end
If I do the following:
a = A.create(:name => "a1")
b = B.create(:name => "b1")
b.a = a
Should I expect the a.b_ids array to be an array that contains b's id?
The behaviour I am seeing is that b.a_id contains a's id, but a.b_ids does not contain b's id.
Is the id array on A's side supposed to be manually updated?
BTW, if I do a.bs << b, then a.b_ids gets updated correctly.
To answer my own question, the id arrays are not automatically set at the moment.
This feature is planned to be included once the refactor branch of mongoid is released.
This info comes from this thread: http://groups.google.com/group/mongoid/browse_thread/thread/9ac74dc9a08a5fe2/d3a7c2404b67abfa
Until then, the ids have to be tracked manually.
An example would be:
class A
include Mongoid::Document
field :name
attr_accessible :name, :b_ids
references_many :bs, :stored_as :array, :inverse_of :a
def add_b b
bs << b
self.save
end
def remove_b b
b_ids.delete b.id
b.save
end
end
class B
include Mongoid::Document
field :name
attr_accessible :name, :a_id
referenced_in :a
end
a = A.create(:name => "a1")
b = B.create(:name => "b1")
b.a = a
a.add_b b

Resources