Please check my understanding of how recursive destroys work?
I have a blog object that contains a lot of posts. The posts go on to have a newsfeed object that is created every time a post is created. When I delete the blog, the posts are deleted, but the newsfeed objects on the posts are not being deleted, leaving me with 'ghost' newsfeed objects.
models > blog.rb
class Blog < ActiveRecord::Base
attr_accessible :description, :title, :user_id, :cover
belongs_to :user
has_many :posts, :dependent => :destroy
end
models > post.rb
class Post < ActiveRecord::Base
attr_accessible :blog_id, :content_1, :type, :user_id, :media, :picture, :event_id
belongs_to :blog
belongs_to :user
end
So when I call for a blog to be destroyed, it's picking up all the posts and destroying them. That's great! But I have a special piece of custom code in the post controller's destroy function that calls for custom destruction of the newfeeds. That's not being called.
controllers > post_controller.rb
def destroy
#post = Post.find(params[:id])
# Delete Feed on the creation of the post
if Feed.exists?(:content1 => 'newpost', :content2 => params[:id])
#feeds = Feed.where(:content1 => 'newpost', :content2 => params[:id])
#feeds.each do |feed|
feed.destroy
end
end
#post.destroy
respond_to do |format|
format.html { redirect_to redirect }
format.json { head :no_content }
end
end
That bit of code in the post's destroy function is not being called, so the newfeed objects are not being destroyed. Is my understanding of the dependency destroy functionality wrong?
I specifically would like to avoid creating a belongs_to and has_many relationship between newsfeeds and post objects, because newsfeed objects are triggered by other types of user actions, like friending someone new, or creating a new blog, differentiated by the type of newsfeed it is in the content1 variable.
I would suggest moving the custom Feed-deletion code into your Post model like do:
class Post
before_destroy :cleanup
def cleanup
# Delete Feed on the creation of the post
if Feed.exists?(:content1 => 'newpost', :content2 => id)
#feeds = Feed.where(:content1 => 'newpost', :content2 => id)
#feeds.each do |feed|
feed.destroy
end
end
end
end
Now, if #feeds is empty, then it's probably a problem with the exists? function. But moving this code to this callback function will ensure that anytime a Post is deleted, the associated feeds get deleted.
In your controller, just call #post.destroy as normal, and the rest will take care of itself.
Related
I am getting a 'Couldn't find Location with ID=4 for Post with ID=' error within PostsController#create when user creates the new post with nested location form.
In this case, post has one location and user has many locations through posts.
Users can create the first new post successfully, but the error occurs when users create new post again.
PostsController:
class Categories::PostsController < ApplicationController
...
def new
#post = Post.new
if current_user.locations
#post.location = current_user.locations.first
else
#post.build_location
end
end
def create
#post = current_user.posts.build(post_params)
#post.category = #category
if #post.save
flash[:success] = "You have succesfully created a new post."
redirect_to [#category, #post]
else
flash[:danger] = "Error occured. Please try again."
render :new
end
end
...
private
def post_params
params.require(:post).permit(:title, :body, location_attributes: [:id, :street, :city, :zipcode, :_destroy])
end
end
Here #post = current_user.posts.build(post_params) in create action throws the error.
I noticed that when I removed location_attributes: [:id], users are able to create new posts. But without whitelisting the location id, the server log has "Unpermitted parameter id" error and also users can't remove the location nested form when editing posts.
I understand that the create method will work by removing:
if current_user.locations
#post.location = current_user.locations.first
However, my purpose is to retrieve the previous location data from users so they don't need to put down the same address again in new posts.
Also, I would like the post can be created without filling out the location form.
Thank you for your help in advance!
Update:
I have tried changing the association for User, Location, and Post models, but it didn't fix the same error I got.
post.rb
belongs_to :location
belongs_to :user
accepts_nested_attributes_for :location
location.rb
has_many :posts
belongs_to :user
user.rb
has_many :posts
has_many :locations
What other methods can help fix the error?
This is a situation when your Post should belong_to a Location, rather than have have_one. This will put a location_id foreign key on the the posts table, which should be on the model you're going though, ie through: posts.
Currently, when you call #post.save in your controller, it will try to query the db for a Post that has not yet been saved to the database, giving you that error.
post.rb
belongs_to :location
belongs_to :user
accepts_nested_attributes_for :location
location.rb
has_one :post
user.rb
has_many :posts
has_many :locations, through: :posts
Lastly, you'll need to add the reference to the db.
rails g migration AddLocationRefToPosts location:references
Your controller code looks good as far as I can tell and should work after these changes.
I'm trying to create a model where every blog post has a number of tags (a separate model) associated to it. In my form, I pass back a separated string of tags along with the post, like "apples,oranges,bananas". I'm eventually going to try and split this string and then create a tag for each string. However, for now I'm just trying to see if I can make one tag with the whole string.
The request is handled by the create method of the posts controller. However, I can't figure out how to create and commit the tag in the posts controller, or at least call the tags controller to delegate the creation. I get the error:
undefined method `new=' for Tag(id: integer, tag_name: string, post_id: integer):Class
This points to the line Tag.new = #post.tags.create(post_params[:tag_name]) of the posts_controller.
Relevant code:
posts_controller
def new
#post = Post.new
end
def create
#post = current_user.posts.create(post_params)
Tag.new = #post.tags.create(post_params[:tag_name])
if #post.save
redirect_to post_path(#post), :notice => "Post was created successfully."
else
render :new
end
end
private
def post_params
params.require(:post).permit(:title, :content_md, :image, :user_id, :year_created, :medium, :dimension_height, :dimension_width, :measurement, :weight_in_pounds, :price, :quantity)
end
tags_controller
class TagsController < ApplicationController
def new
#tag = Tag.new
end
def create
#tag = tag.create(tag_params)
end
private
def tag_params
params.require().permit(:tag_name, :user_id)
end
end
post.rb
class Post < ActiveRecord::Base
# Relations
belongs_to :user
has_many :comments
has_many :post_votes
has_many :tags
accepts_nested_attributes_for :tags
end
tag.rb
class Tag < ActiveRecord::Base
validates :tag_name, presence: true
validates :post_id, presence: true
belongs_to :post
end
Edit:
Also tried:
def create
#tag = Tag.new(post_params[:tag_name])
#tag.save
#post = current_user.posts.create(post_params)
# #post.tags.create(post_params[:tag_name])
if #post.save
redirect_to post_path(#post), :notice => "Post was created successfully."
else
render :new
end
end
for the posts controller. It creates the post, but doesn't seem to actually save the #tag
Without going into much deep, I see you you've the following line in your code (in posts_controller)
Tag.new = #post.tags.create(post_params[:tag_name])
which is not correct. If you want to just create (but not persist) you should call,
#post.tags.new(post_params[:tag_name])
For persist
#post.tags.create(post_params[:tag_name])
This will get rid of the error that you've posted. However, you shouldn't require these as your Post model can accept nested attributes for tags. Just make sure you've generated form correctly (hint: fields_for)
This line
Tag.new = #post.tags.create(post_params[:tag_name])
has no sense. Without further discussion:
#post.tags.create(post_params[:tag_name])
will work and actually create the tag.
posts 1--* post_tags *--1 tags
A post has many tags and a tag has many posts. They are related through the post_tags table.
post.rb
class Post < ActiveRecord::Base
attr_accressible :tag_ids
has_many :post_tags
has_many :tags, :through => :post_tags
end
tag.rb
class Tag < ActiveRecord::Base
has_many :post_tags
has_many :posts, :through => :post_tags
end
post_tag.rb
class PostTag < ActiveRecord::Base
belongs_to :post
belongs_To :tag
end
posts_controller.rb
class PostsController < ApplicationController
def new
#post = Post.new
end
def create
#post = Post.new(params[:post])
#post.save ? redirect_to(:action => 'index') : render(:action => 'new')
end
end
The tags table is a catalog and I just want the user to select the appropriate ones for the post.
So in my view I have a multiple select:
new.html.erb
post_form.collection_select(:tag_ids, #tags, nil, nil, {}, { :multiple => true })
This works but the problem is when I send invalid ids I get this error
ActiveRecord::RecordNotFound in Posts#create
Couldn't find all Tags with IDs (1, 200) (found 1 results, but was looking for 2)
Tag with id 1 exists but tag with id 200 doesn't.
Any ideas?
Got your problem, when you send in some invalid tag_id say, 200 the Rails will first search whether the Tag with ID 200 actually exist or not. So the best way to tackle this problem is to use a before filter where you make sure that you get the correct or valid ids. And then you can simply do an assignment which would assign the tag_ids to the post....You can do something like this
before_filter :find_tags_from_ids
def find_tags_from_ids
#tags = Tag.where(:id => params[:post][:tag_ids]
end
def create
#post = Post.new(params[:post])
#post.tags << #tags
#post.save ? redirect_to(:action => 'index') : render(:action => 'new')
end
This would solve your problem and hopefully you won't get an exception this time..
Hi I'm currently building a little forum application with rails (3). I'm fairly new in the Rails matter and I got stuck with the topics.
I have 2 models (topic & topic_reply)
topic model:
class Topic < ActiveRecord::Base
belongs_to :board
belongs_to :user
has_many :topic_replies, :dependent => :destroy
TOPIC_TYPES = ["Non-support", "Question"]
validates :topic_type, :inclusion => TOPIC_TYPES
end
topic reply model:
class TopicReply < ActiveRecord::Base
belongs_to :topic
belongs_to :user
end
When I'm creating a topic everything is displaying fine except for the topic_replies (all the posts within the topic)
The reason is: my topic_reply object saves everything correctly except it doesn't save the the topic_id in the object and thus in my db.
here's a part of my topic controller to create a topic:
# GET /topics/new
# GET /topics/new.xml
def new
#topic = Topic.new
#topic_reply = #topic.topic_replies.build
respond_to do |format|
format.html # new.html.erb
end
end
# POST /topics
# POST /topics.xml
def create
#topic = Topic.new(params[:topic])
#topic.id_board = #board.id
#topic_reply = #topic.topic_replies.build(params[:topic_reply])
#topic_reply.id_topic = #topic.id
#topic_reply.id_poster = User.first.id
respond_to do |format|
if #topic.save && #topic_reply.save
format.html { redirect_to(topic_path("#{#board.name.parameterize}-#{#board.id}", "#{#topic.title.parameterize}-#{#topic.id}")) }
else
format.html { render :action => "new" }
end
end
end
does anyone have an idea what I'm doing wrong if I posted too less information let me know I'll add what you need.
Thanks in advance
Your foreign keys should be named topic_id, poster_id and board_id as these are default values in Rails. You're not defining non-default columns in your models, so the current code can't work.
I have a survey as part of an application I'm building. The user can create a survey and specify questions dynamically (can have as many as they want), so I've used an associated model with:
#survey.rb
has_many :survey_questions, :dependent => :destroy
has_many :survey_answers, :dependent => :destroy
after_update :save_survey_questions
validates_associated :survey_questions
def save_survey_questions
survey_questions.each do |t|
if t.should_destroy?
t.destroy
else
t.save(false)
end
end
end
def survey_question_attributes=(survey_question_attributes)
survey_question_attributes.each do |attributes|
if attributes[:id].blank?
survey_questions.build(attributes)
else
survey_question = survey_questions.detect { |e| e.id == attributes[:id].to_i }
survey_question.attributes = attributes
end
end
end
#surveys_controller.rb
def new
#survey = Survey.new
if(#survey.survey_questions.empty?)
#survey.survey_questions.build
end
respond_to do |format|
format.html # new.html.erb
end
end
def create
#survey = Survey.new(params[:survey])
respond_to do |format|
if #survey.save
format.html { redirect_to(survey_path(:id => #survey)) }
else
format.html { render :action => "new" }
end
end
end
#survey_question.rb
class SurveyQuestion < ActiveRecord::Base
belongs_to :survey
attr_accessor :should_destroy
def should_destroy?
should_destroy.to_i == 1
end
validates_presence_of :question, :survey_id
end
The problem is when I submit I get an error on the questions:
#errors={"survey_questions"=>["is invalid", "is invalid", "is invalid"]}
I believe it is because the survey_id I have linking surveys to survey_questions is not being filled in.
Any ideas how I can overcome this?
If I create the survey with no questions, then add them afterwards via edit, then it works perfectly.
I'm pretty sure that accepts_nested_attributes can help you a lot, there you'll find some examples building the associated objects wich seems to be your problem (since the survey_id in survey_questions is not being filled in), basically you should define in your models something like:
class Survey < ActiveRecord::Base
has_many :survey_questions
accepts_nested_attributes_for :survey_questions, :allow_destroy => true
...
end
It will handle all the SurveyQuestions validations through Survey.
Your code looks close to the standard --> http://railscasts.com/episodes/75-complex-forms-part-3
Are you sure you are getting the question parameter back correctly? I only ask because that is the other thing you are validating against and you don't have the form code in there so I can't see what's coming back to the controller.
OK,
I've managed to fix it and it was a really silly mistake on my part.
In survey_question.rb I had the line :
validates_presence_of :question, :survey_id
However, rails automatically deals with survey_id because of the has_many belongs_to relationship!
So this should be validates_presence_of :question
I also in the process of finding this out upgraded rails to 2.3.4 and started using:
accepts_nested_attributes_for :survey_questions, :allow_destroy => true
which dealt with all the attributes etc.