I'll admit I don't exactly know why a before filter is (or even if it is) the best way to handle my problem, but I've been told by a developer who knows a helluva lot more than I do about Rails programming that it is. So I'm gonna try and make it work!
So what I'm trying to do is check to see if the latest book in the database was created 7 days ago or more, and if so, create a new one.
Here's what my books controller currently looks like:
class BooksController < ApplicationController
before_filter :check_seven_days, :only => [:create]
...
def create
#book = Book.new(params[:book])
respond_to do |format|
if #book.save
format.html { redirect_to user_url(#book.user), notice: 'Book was successfully added to your queue.' }
format.json { render json: #book, status: :created, location: #book }
else
format.html { render action: "new" }
format.json { render json: #book.errors, status: :unprocessable_entity }
end
end
end
...
protected
def check_seven_days
#user = User.find(params[:id])
#not_queued_books = #user.books.not_queued
#not_queued_books.each do |book|
Book.new if book.created_at >= 7.days.ago
end
end
end
However that's not exactly working...at all. The code in the before filter is more or less pseudo-code. We'll call it that because I'm still learning how to write Ruby properly! But hopefully you can get the point of what I'm trying to do :)
And also, so you can see where this is coming from, I am using scopes in the model to check if a book has been added more than 25 seconds ago:
scope :queued, lambda { where('created_at > ?', 25.seconds.ago) }
scope :not_queued, lambda { where('created_at <= ?', 25.seconds.ago) }
scope :date_desc, order("created_at DESC")
Also, the view loop (in the users show view) looks like this:
<% #not_queued_books.date_desc.each do |book| %>
<%= book.title %>
<%= book.author %>
<% end %>
Book.new is just going to instantiate a new Book object without saving and without any parameters; did you mean:
Book.create(params[:book])
?
I'm currently trying to create a survey which will use questions that are stored in a table. I've read nested model form part 1 from rails casts however i'm not getting anywhere as the questions are not displaying in the survey.
I have three tables, one tables has the text of the questions, another table keeps the record of who entered the survey and a third table which keeps the answers from a user for the questions.
variable table:
name: varchar
id: integer
report table
employee name: varchar
date: date
id: integer
report_variable table
question_id
report_id
answer
Code i modified for reports/new:
# GET /reports/new
# GET /reports/new.json
def new
#report = Report.new
#variable = #report.variable.build #dont know what to do here, gives an error with report_id
respond_to do |format|
format.html # new.html.erb
format.json { render json: #report }
end
end
modified report/_form.html.erb
<div >
<%= f.fields_for :variable do |builder| %>
<%= render variable_fields, :f => builder %>
<% end %>
</div>
created report/_variable_fields.html.erb
<p>
<%= f.label :name %>
<%= f.text_field :name, :class => 'text_field' %>
<p>
model for report_variable
class ReportVariable < ActiveRecord::Base
attr_accessible :report_id, :value, :variable_id
has_and_belongs to many :reports
has_and_belongs to many :variables
end
model for report
class Report < ActiveRecord::Base
attr_accessible :employeeName
has_many :report_variable
has_many :variable
accepts_nested_attributes_for :report_variable
accepts_nested_attributes_for :variable
end
Sorry if it's a simple question, im pretty new to rails.
Welcome to Rails!
I think the simple answer is the fields aren't showing up because there aren't any nested records. You can probably get around that by uncommenting the variable line as you have it:
def new
#report = Report.new
#report.variables.build #this line creates 1 new empty variable, unsaved.
respond_to do |format|
format.html # new.html.erb
format.json { render json: #report }
end
end
If you want more than one variable, call something like:
3.times { #report.variables.build }
That way the #report object you're placing in the form helper will have three variables on it. This should get you moving again, the harder thing is going to be adding ajax addition / removal of variables, but if you know how many there are in advance you don't have to deal with that.
Good luck!
I get an error with the following code, but with a logger row or debug is the output correct. It's a little bit strange for me. (Rails 3.1.0 and 3.0.9 and Ruby 1.8.7)
Controller contains:
def index
#privatmessages = Privatmessage.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #privatmessages }
end
end
index.htm.erb
<% #privatmessages.each do |privatmessage| %>
<%= privatmessage.id %>
<% end %>
That code produce the error:
ArgumentError in Privatmessages#index
Showing ../app/views/privatmessages/index.html.erb where line #2 raised:
wrong number of arguments (1 for 0)
But the output is correct and without errors, if I add the following line at the controller:
logger.info "Messages: {##privatmessages.to_yaml}"
or if I add inside of the each loop at the index.html.erb the line:
<%= debug privatmessage %>
Has anyone an advice for me?
Issue found and solved!
The problem was that I used "send" as a column name in the table, but "send" is a reserved method for Rails core. After renaming the column in the table to "sendout" it works.
I have question model which has many options.
In my question controller new action I create five options ready for my user
def new
#question = Question.new
5.times.with_index do |index|
#question.options.build(:order => index)
end
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #question }
end
end
In the view I loop through all options
- form_for(#question) do |f|
.field
= f.label :title, t("question.title")
= show_errors_for(#question, :title)
= f.text_field :title
- #question.options.each do |option|
- f.fields_for :options, option do |o|
.field
= o.label :option, t("question.option_no", { :index => option.order })
= o.text_field :option
= o.hidden_field :order, :value => option.order
.actions
= f.submit t("add_question.create")
My question model looks like this
class Question < ActiveRecord::Base
attr_accessible :title, :options_attributes
belongs_to :user
has_many :options
accepts_nested_attributes_for :options, :reject_if => proc { |attributes| attributes['option'].blank? }
validates :title, :length => { :maximum => 100 }, :presence => true
validate :min_no_of_options
def min_no_of_options
if self.options.size < 3
errors.add_to_base "Must have at least three options"
end
end
end
And my question controller create action
def create
if current_user
#question = current_user.questions.build(params[:question])
else
#question = Question.new(params[:question])
end
if #question.save
redirect_to(#question, :success => t('question.flash_success'))
else
flash.now[:error] = t("question.flash_error")
render :action => "new"
end
end
Now, when I enter only two options in the form and hit the create button the validation prevents the model from being saved. Which is good. But when the create action renders the new action again, only the option fields that I filled are showing up. The three option fields which were left blank have disappeared.
If I replace the "#question.save" in my create action with "false", the behavior is the same. So this suggests that something in the way I create the #question variable in the create action is responsible for throwing away my empty options.
But if I instead remove the :reject_if from my question model the empty options are showing up after a failing question save as expected. (I have a presence validation for the option attribute in my option model) So this tells me that there is nothing wrong in the way I create the #question variable in the create action. It is not throwing away the empty options. So where they are kicked out?
There was one pretty similar question, but the answer in there is not something I would like to do. Though it might be something I have to do.
rails fields_for does not render after validation error on nested form
EDIT
After some more study with rails console I noticed that it truly is the creation of #question variable where the empty options get thrown away. This happens because I have the reject_if defined in the question model. After commenting the reject_if out from the model the empty options were added into the #question variable.
So I guess I need to remove the reject_if and use after_save callback to destroy empty options from the database. This way I will have the empty options going along with the question until the question is saved.
I'm answering my own question because I got the issue solved.
Blank "options" were removed from the "question" because of reject_if in the "question" model. The reject_if statement was applied when the code below was executed, and therefore blank "options" were removed.
#question = current_user.questions.build(params[:question])
I replaced the reject_if with an after_save callback which removes the options which were left blank.
Fairly new to Rails 3 and have been Googling every which way to no avail to solve the following problem, with most tutorials stopping short of handling errors.
I have created a Rails 3 project with multiple content types/models, such as Articles, Blogs, etc. Each content type has comments, all stored in a single Comments table as a nested resource and with polymorphic associations. There is only one action for comments, the 'create' action, because there is no need for the show, etc as it belongs to the parent content type and should simply redisplay that page on submit.
Now I have most of this working and comments submit and post just fine, but the last remaining issue is displaying errors when the user doesn't fill out a required field. If the fields aren't filled out, it should return to the parent page and display validation errors like Rails typically does with an MVC.
The create action of my Comments controller looks like this, and this is what I first tried...
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to(#commentable, :notice => 'Comment was successfully created.') }
else
format.html { redirect_to #commentable }
format.xml { render :xml => #commentable.errors, :status => :unprocessable_entity }
end
end
end
When you fill nothing out and submit the comments form, the page does redirect back to it's appropriate parent, but no flash or nothing is displayed. Now I figured out why, from what I understand, the flash won't persist on a redirect_to, only on a render. Now here's where the trouble lies.
There is only the 'create' action in the comment controller, so I needed to point the render towards 'blogs/show' (NOTE: I know this isn't polymorphic, but once I get this working I'll worry about that then). I tried this in the "else" block of the above code...
else
format.html { render 'blogs/show' }
format.xml { render :xml => #commentable.errors, :status => :unprocessable_entity }
end
Anyway, when I try to submit an invalid comment on a blog, I get an error message saying "Showing [...]/app/views/blogs/show.html.erb where line #1 raised: undefined method `title' for nil:NilClass."
Looking at the URL, I think I know why...instead of directing to /blogs/the-title-of-my-article (I'm using friendly_id), it's going to /blogs/the-title-of-my-article/comments. I figure that extra "comments" is throwing the query off and returning it nil.
So how can I get the page to render without throwing that extra 'comments' on there? Or is there a better way to go about this issue?
Not sure if it matters or helps, but the route.rb for comments / blogs looks like this...
resources :blogs, :only => [:show] do
resources :comments, :only => [:create]
end
I've been plugging away at this over the last few weeks and I think I've finally pulled it off, errors/proper direction on render, filled out fields remain filled in and all. I did consider AJAX, however I would prefer to do it with graceful degradation if at all possible.
In addition, I admit I had to go about this a very hacky-sack way, including pulling in a way to pluralize the parent model to render the appropriate content type's show action, and at this stage I need the code to simply work, not necessarily look pretty doing it.
I KNOW it can be refactored way better, and I hope to do so as I get better with Rails. Or, anyone else who thinks they can improve this is welcomed to have at it. Anyway, here is all my code, just wanted to share back and hope this helps someone in the same scenario.
comments_controller.rb
class CommentsController < ApplicationController
# this include will bring all the Text Helper methods into your Controller
include ActionView::Helpers::TextHelper
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to(#commentable, :notice => 'Comment was successfully created.') }
else
# Transform class of commentable into pluralized content type
content_type = find_commentable.class.to_s.downcase.pluralize
# Choose appropriate instance variable based on #commentable, rendered page won't work without it
if content_type == 'blogs'
#blog = #commentable
elsif content_type == 'articles'
#article = #commentable
end
format.html { render "#{content_type}/show" }
format.xml { render :xml => #commentable.errors, :status => :unprocessable_entity }
end
end
end
private
# Gets the ID/type of parent model, see Comment#create in controller
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
end
end
articles_controller.rb
class ArticlesController < ApplicationController
def show
#article = Article.where(:status => 1).find_by_cached_slug(params[:id])
#comment = Comment.new
# On another content type like blogs_controller.rb, replace with appropriate instance variable
#content = #article
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #article }
end
end
end
show.html.erb for articles (change appropriate variables for blog or whatever)
<h1><%= #article.title %></h1>
<%= #article.body.html_safe %>
<%= render :partial => 'shared/comments', :locals => { :commentable => #article } %>
shared/_comments.html.erb (I'm leaving out the displaying of posted comments here for simplification, just showing the form to submit them)
<%= form_for([commentable, #comment]) do |f| %>
<h3>Post a new comment</h3>
<%= render :partial => 'shared/errors', :locals => { :content => #comment } %>
<div class="field">
<%= f.label :name, :value => params[:name] %>
<%= f.text_field :name, :class => 'textfield' %>
</div>
<div class="field">
<%= f.label :mail, :value => params[:mail] %>
<%= f.text_field :mail, :class => 'textfield' %>
</div>
<div class="field">
<%= f.text_area :body, :rows => 10, :class => 'textarea full', :value => params[:body] %>
</div>
<%= f.submit :class => 'button blue' %>
<% end %>
shared/_errors.html.erb (I refactored this as a partial to reuse for articles, blogs, comments, etc, but this is just a standard error code)
<% if content.errors.any? %>
<div class="flash error">
<p><strong><%= pluralize(content.errors.count, "error") %> prohibited this page from being saved:</strong></p>
<ul>
<% content.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
I slightly refactored #Shannon answer to make it more dynamic. In my 'find_parent' method I'm grabbing the url path and fetching the controller name. In the 'create' method I'm creating an 'instance_variable_set' which creates a dynamic variable for either Articles (#article) or Blogs (#blog) or what ever it may be.
Hopefully you'll like what I've done? Please let me know if you have any doubts or if something can be improved?
def create
#comment = #commentable.comments.new(params[:comment])
if #comment.save
redirect_to #commentable, notice: "Comment created."
else
content_type = find_parent
instance_variable_set "##{content_type.singularize}".to_sym, #commentable
#comments = #commentable.comments
render "#{content_type}/show"
end
end
def find_parent
resource = request.path.split('/')[1]
return resource.downcase
end
You're getting an error because the blogs/show view likely refers to the #blog object, which isn't present when you render it in the comments controller.
You should go back to using the redirect_to rather than render. It wasn't displaying a flash when you made an invalid comment because you weren't telling it to set a flash if the comment wasn't saved. A flash will persist till the next request.