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.
Related
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.
I'm having a problem based on the excellent RailsCast #258 from Ryan Bates.
The situation is as follows:
class User < ActiveRecord::Base
has_many :capabilities,
:dependent => :destroy
has_many :skills, :through => :capabilities,
:uniq => true
has_many :raters,
:through => :capabilities,
:foreign_key => :rater_id,
:uniq => true
attr_accessible :name, :skill_tokens
attr_reader :skill_tokens
def skill_tokens=(tokens)
self.skill_ids = Skill.ids_from_tokens(tokens)
end
end
class Capability < ActiveRecord::Base
belongs_to :user
belongs_to :rater, class_name: "User"
belongs_to :skill
validates_uniqueness_of :rater_id, :scope => [:user_id, :skill_id]
end
class Skill < ActiveRecord::Base
has_many :capabilities
has_many :users, :through => :capabilities,
:uniq => true
has_many :raters, :through => :capabilities,
:foreign_key => :rater_id
end
The form contains a normal textfield for the skill tokens which are passed as ids:
.field
= f.label :skill_tokens, "Skills"
= f.text_field :skill_tokens, data: {load: #user.skills}
So a user can get many skills assigned through capabilities. While assigning the skill, the rater should also be tracked in the capability model.
Using Ryans example of jquery TokenInput I created an appropriate form to allow a user to assign (and create) skills using a tokenInput text field.
The Problem lies now in processing the data and setting the rater before the association is saved.
Through some ruby magic, self.skill_ids on the user model sets the ids used for the association model creation so the controller action is quite simple:
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
Obviously, if I want to set the additional rater attribute on the capability model it won't work so easily with update_attributes.
So how can I achieve this with "the rails way" to do it - writing beautiful, readable code?
ANY help would be greately appreciated!
How are you setting the rater_id?
If you plan accept a user input for the rater for each skill the user adds on the form,
I can't see how you'll be able to use input fields based on token inputs to achieve this. You're going to have to choose some other types of inputs.
If you plan to set the rater to the currently logged in user, or are setting the rater based on some other business logic, my approach would be overwriting the skill_ids= method in the User model to work how you want it, adding an attr_accessor to store the current_rater and passing the current_rate from the controller.
Something like:
#user.rb
attr_accessor :current_rater
def skill_ids=(ids)
return false if current_rater.nil? || User.find_by_id(current_rater).nil?
capabilities.where("skill_id not in (?)", ids).destroy_all
ids.each do |skill_id|
capabilities.create(:skill_id => skill_id, :rater_id => self.current_rater) if capabilities.find_by_id(skill_id).nil?
end
end
#users_controller.rb
def update
#user = User.find(params[:id])
#Replace 'current_user' with whatever method you are using to track the logged in user
params[:user].merge(:current_rater => current_user)
respond_to do |format|
...
end
end
Probably not as elegant as you were hoping, but it should do the job?
i'm really new to Rails and i'm wondering, how the following could be done:
After an user has written a comment for a sin ( =article), the author ( =user) should get 20 points (for example) added to his score (= user.score). score is a column in my Users table.
My models look like this:
class User < ActiveRecord::Base
has_many :comments, :dependent => :destroy
has_many :absolutions, :dependent => :destroy
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :sin
end
class Sin < ActiveRecord::Base
has_many :comments, :dependent => :destroy
end
My comments controller looks like this:
class CommentsController < ApplicationController
def new
#comment = Comment.new
end
def create
#sin = Sin.find(params[:sin_id])
#comment = current_user.comments.build(params[:comment])
#comment.sin_id = #sin.id
if #comment.save
flash[:success] = "Comment created!"
redirect_to sin_path(#sin)
else
flash[:error] = "Comment was not created."
redirect_to sin_path(#sin)
end
end
end
After spending several hours to get this on my own, i'm a little confused. After creating a comment, i would like change a specific value of the associated object User.
What would be the best way to do this?
Thanks for your help!
You could just add it after save :
if #comment.save
flash[:success] = "Comment created!"
current_user.score += 20
current_user.save
redirect_to sin_path(#sin)
else
BUT, it's always better to do it in your model. So i would create an add_score instance method in your user model and update the score there. Then, i would just call that method in the controller, in the same spot.
Define an after_save callback in your comment model:
class Comment < ActiveRecord::Base
[...]
after_save :add_score
private
def add_score
self.user.score += 20
self.user.save
end
end
You could use an after_create callback in your comment model that makes the change in the corresponding user?
This kind of logic doesn't belong in the controller.
I am trying to limit a user of my application to voting (liking in this case) a an answer to a question a particular number of times. I am successfully stopping the collection of the user_id but the record keeps getting created. I need for the validation to actually block the creation of the record in the likes table.
alt text http://gadocidesign.squarespace.com/storage/Screen%20shot%202010-05-20%20at%2010.07.19%20AM.png
As you can see the last two votes lose the user_id but are still created. The code below will show you how I am doing this. I am trying to limit a user to voting no more than 10 times on any answer to a question.
Like Model (I spare you the reverse has_many associations but they are there).
class Like < ActiveRecord::Base
belongs_to :site
belongs_to :user
belongs_to :question
validates_each :user_id do |row, attr, value|
m.errors.add :user_id, 'Too many likes' unless row.like_count < 10
end
def like_count
Like.count(:conditions => {:user_id => user_id, :question_id => question_id})
end
end
LikesController#create
class LikesController < ApplicationController
def create
#user = current_user
#site = Site.find(params[:site_id])
#like = #site.likes.create!(params[:like])
#like.user = current_user
#like.save
respond_to do |format|
format.html { redirect_to #site}
format.js
end
end
end
You're telling it to do exactly what you're seeing.
# This is what creates the record you're seeing in your db.
#like = #site.likes.create!(params[:like])
# And now you try to assign the user.
#like.user = current_user
#like.save
Try something like this instead:
#like = #site.likes.create!(params[:like].merge(:user_id => #user.id))
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.