How to save embedded classes in mongoid? - ruby-on-rails

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

Related

Mongoid::Errors::MixedRelations in AnswersController#create

Getting following error msg while saving an answer:
Problem: Referencing a(n) Answer document from the User document via a relational association is not allowed since the Answer is embedded. Summary: In order to properly access a(n) Answer from User the reference would need to go through the root document of Answer. In a simple case this would require Mongoid to store an extra foreign key for the root, in more complex cases where Answer is multiple levels deep a key would need to be stored for each parent up the hierarchy. Resolution: Consider not embedding Answer, or do the key storage and access in a custom manner in the application code.
Above error is due to the code #answer.user = current_user in AnswersController.
I want to save the login username to the answer which is embaded in question.
deivse User model:
class User
include Mongoid::Document
has_many :questions
has_many :answers
class Question
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Slug
field :title, type: String
slug :title
field :description, type: String
field :starred, type: Boolean
validates :title, :presence => true, :length => { :minimum => 20, :allow_blank => false }
embeds_many :comments
embeds_many :answers
#validates_presence_of :comments
belongs_to :user
end
class Answer
include Mongoid::Document
include Mongoid::Timestamps
field :content, type: String
validates :content, :presence => true, :allow_blank => false
embedded_in :question, :inverse_of => :answers
#validates_presence_of :comments
belongs_to :user
end
class AnswersController < ApplicationController
def create
#question = Question.find(params[:question_id])
#answer = #question.answers.create(params[:answer].permit(:answerer, :content))
#answer.user = current_user
redirect_to #question, :notice => "Answer added!"
end
end
Using Rails 4, Ruby 2.2.2, Mongoid.
That's exactly what the error message says.
Your Answer model is embedded in the question model. That is to say, you can only perform "normal" queries on the Question documents, and not on the models embedded in this one (actually you can, but it's more difficult and somehow kills the point of using embedded documents).
So you can get the user for a given answer, but not the inverse, which you have declared in your user model.
The simplest solution is to remove has_many :answers from the user model, but if you want to retrieve the list of answers for a given user, then embedding models is probably not the best solution: you should have relational models.
To make things clear, you should write belongs_to :user, inverse_of: nil

Validate before Posting a text or an image

I am working on a self-learning Rails application (the source code can be found here. I want to validate the presence of the content before posting a text or an image:
.
Those are my models or look below:
class Post < ActiveRecord::Base
belongs_to :user
default_scope { order ("created_at DESC")}
belongs_to :content, polymorphic: true
has_reputation :votes, source: :user, aggregated_by: :sum
end
class PhotoPost < ActiveRecord::Base
has_attached_file :image, styles: {
post: "200x200>"
}
end
class TextPost < ActiveRecord::Base
attr_accessible :body
end
Here are my controllers in case they have a relation with this. Any other files can be found in my Github account. I am sure it will be messy to copy the whole project (that is why I am giving links for the controllers and for my project).
So what I have tried so far. (I tried those on the Posts Model)
=> Using validates_associated
validates_associated :content, :text_post
and getting an error "undefined method `text_post' for #Post:0x517c848>"
=> Used validates
validates :content, :presence => true
and getting no error however a post is created with no text.
validates :body, :presence => true
and getting an error "undefined method `body' for #Post:0x513e4a8>"
If you need any other information please let me know and I will provide it asap.
Thank you.
It would seem you have quite a confusing model setup with some key missing relation rules. E.g. Polymorphic rule which is not being utilised and a has_many relation between User and Post with no sign a of a user_id value in the Post model. Here is how I would set it up:
User.rb
def User << ActiveRecord::Base
has_many :text_posts
has_many :photo_posts
end
TextPost.rb
def TextPost << ActiveRecord::Base
attr_accessible :body, :user_id
belongs_to :user
validates :body, :presence => true
end
PhotoPost.rb
def PhotoPost << ActiveRecord::Base
attr_accessible :image, :user_id
belongs_to :user
validates :file, :presence => true, :format => {
:with => %r{\.(gif|png|jpg)$}i,
:message => "must be a URL for GIF, JPG or PNG image."
}
end
Then in your view you would need to do:
<%= form_for #text_post do |f| %>
# ...
<% end %>
And in your controller you can modify the create method to include the current_user from devise and assign it to the new text post record (user_id attribute):
text_posts_controller.rb
def create
#text_post = current_user.text_posts.new(params[:text_post])
end
This adheres more to the DRY principle which Ruby on Rails excels at - you shouldn't be writing alot of code to just create a new record.
I would advise on reading up on some Ruby on Rails standard and best practises. You shouldn't need to create a method in the Dashboard Model in order to create a new TextPost or PhotoPost record. This is a very confusing way of going about it; instead you should be utilising the power of ActiveRecord relation.
I would advise checking out Railscasts. They have alot of fulfilling content.

Sort by latest created comment

What i have created is a "active" field in my topics table which i can use to display the active topics, which will contain at first the time the topic was created and when someone comments it will use the comment.created_at time and put it in the active field in the topics table, like any other forum system.
I found i similar question here
How to order by the date of the last comment and sort by last created otherwise?
But it wont work for me, im not sure why it wouldn't. And i also don't understand if i need to use counter_cache in this case or not. Im using a polymorphic association for my comments, so therefore im not sure how i would use counter_cache. It works fine in my topic table to copy the created_at time to the active field. But it wont work when i create a comment.
Error:
NoMethodError in CommentsController#create
undefined method `topic' for
Topic.rb
class Topic < ActiveRecord::Base
attr_accessible :body, :forum_id, :title
before_create :init_sort_column
belongs_to :user
belongs_to :forum
validates :forum_id, :body, :title, presence: true
has_many :comments, :as => :commentable
default_scope order: 'topics.created_at DESC'
private
def init_sort_column
self.active = self.created_at || Time.now
end
end
Comment.rb
class Comment < ActiveRecord::Base
attr_accessible :body, :commentable_id, :commentable_type, :user_id
belongs_to :user
belongs_to :commentable, :polymorphic => true
before_create :update_parent_sort_column
private
def update_parent_sort_column
self.topic.active = self.created_at if self.topic
end
end
Didn't realise you were using a polymorphic association. Use the following:
def update_parent_sort_column
commentable.active = created_at if commentable.is_a?(Topic)
commentable.save!
end
Should do the trick.

How do you write a Rails model method to return a list of its associations?

What I'm trying to do is write a method that will return all of this model's outing_locations, to which it has a has_many relationship.
class Outing < ActiveRecord::Base
attr_accessible :description, :end_time, :start_time, :title, :user_id
belongs_to :user
has_many :outing_locations
has_many :outing_guests
has_one :time_range, :foreign_key => "element_id", :conditions => { :element_type => "outing" }
validates :title, :presence => true
validates :start_time, :presence => true # regex
validates :end_time, :presence => true # regex
def host_name
return User.find(self.user_id).full_name
end
end
I'm trying to get this block in particular to work.
There's another model called OutingInvite, which contains the id of a particular Outing. I need to use that to grab the proper Outing and then pull said Outing's associated outing locations.
Here's a rough sample:
<%#outing_invites.each do |invite|%>
...
<% #party = Outing.where(:id => invite.outing_id) %>
<% #party.outing_locations.each do |location| %>
And then have it output each location.
However, it's saying the method 'outing_locations' does not exist...
You can see a model's associated models by typing model_instance.associated_model_name. So in your example, an outing has_many outing_locations. After you have an instance of an outing, say by using #o = Outing.find(1), you can then use o.outing_locations to see the outing_locations associated with that specific outing.
See this example from the Ruby on Rails Guide.
EDIT
The reasons you're getting the method 'outing_locations' does not exist error is because Outing.where(:id => invite.outing_id) returns an array, and there is no outing_locations method for arrays. You'll need to either get the specific instance (like with Outing.find(invite.outing_id) or use a specific index in that array. I recommend using Outing.find(invite.outing_id) since (I'm assuming) each of your Outing's has a unique id.

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