I have an model called assignment, which represents a rich join between users and questions in a multiple choice quiz app. when a user answers a question, the response is recorded in the following update method in the assignments controller
def edit
#assignment = Assignment.find_by_id(params[:id])
#user = #assignment.user
#question = #assignment.question
puts "user #{#user} question #{#question} assignment #{#assignment}"
end
def update
#assignment = Assignment.find(params[:id])
#user = #assignment.user
#assignment.update_attributes(:response => params[:assignment][:response])
if #assignment.save
flash[:notice] = "Your response has been saved"
redirect_to(:action => 'show', :id => #assignment.id , :user_id => #user.id)
else
puts #assignment.save
puts "could not save"
render(user_assignment_path(#user, #assignment) , :html => {:method => :get})
end
end
I have a before save called grade that gets called. Here is the model:
class Assignment ActiveRecord::Base
belongs_to :user
belongs_to :question
attr_accessible :title, :body, :user_id, :question_id , :response
before_save :grade
def grade
self.correct = (response == self.question.solution) unless response == nil
end
end
So the first time I submit a response, the save works perfectly and it redirects me accordingly. after that, if i try to edit the question again and resubmit the form, the save fails.
Can anyone think of a reason this might be occurring?
In addition I know there is an error in the second redirect, so if someone could correct my usage that would be a bonus help
EDIT Here is the Edit erb, in case someone can spot something wrong here. I also added the edit method in the controller above.
<div class="admin-display">
<%if #admin%>
<p>
You may edit the user's response below or change the question to override whether the question is marked correct.
</p>
<%end%>
</div>
<div class="question body">
<%= #question.question %>
</div>
<div class="answer-choices">
<%= params[:user_id] + " " + params[:id] %>
<ol type="A">
<%=form_for(#assignment , :url => user_assignment_path(params[:user_id] , params[:id]) , :html => {:method => "put"}, :user_id => params[:user_id]) do |f|%>
<%[#question.answerA, #question.answerB ,#question.answerC ,#question.answerD].each do |choice|%>
<li>
<%= f.radio_button(:response, choice)%>
<%= choice %>
</li>
<%end%>
</ol>
<div class="form-buttons">
<%= submit_tag("Submit Response") %>
</div>
<%end%>
</div>
EDIT 2 I have just gone through the steps by hand in the rails console, without any issue, so there must be something strange going on.
It's probably due to the fact that your grade() callback method is returning a false, which will cancel the action all together (as stated in the documentation).
Hope this will work for you.
In Assignment model, add correct field as attr_accessible. First time as the response is nil, it is not executing the statement in before_save method, Hence your code will be
class Assignment ActiveRecord::Base
belongs_to :user
belongs_to :question
attr_accessible :title, :body, :user_id, :question_id , :response, :correct
before_save :grade
def grade
correct = (response == question.solution) unless response.nil?
end
end
and cotroller action could be
def update
#assignment = Assignment.find(params[:id])
#user = #assignment.user
#assignment.update_attributes(:response => params[:assignment][:response])
if #assignment.valid?
flash[:notice] = "Your response has been saved"
redirect_to(:action => 'show', :id => #assignment.id , :user_id => #user.id)
else
render(user_assignment_path(#user, #assignment) , :html => {:method => :get})
end
end
Related
I'm using Rails 5. I have a form with fields like the below
<%= form_for(#user) do |f| %>
...
<div class="profileField">
<%= f.label :first_name %><br/>
<div class="field"><%= f.text_field :first_name, :size => 20, :class => 'textField', :tabIndex => '1' %></div>
</div>
...
<div class="profileField">
Birthday <% if !#user.errors[:dob].empty? %><span class="profileError"> <%= #user.errors[:dob].join(', ') %></span><% end %> <br/>
<div class="field">
<%= f.text_field :dob_string, :value => f.object.dob_string , :size => "20", :class => 'textField', placeholder: 'MM/DD/YYYY', :tabIndex => 1 %>
</div>
</div>
When the form submits, it invokes this logic in my controller
def update
#user = current_user
#user.dob_string = user_params[:dob_string]
if !#user.errors.any? && #user.update_attributes(user_params)
last_page_visited = session[:last_page_visited]
if !last_page_visited.nil?
session.delete(:last_page_visited)
else
flash[:success] = "Profile updated"
end
redirect_to !last_page_visited.nil? ? last_page_visited : url_for(:controller => 'races', :action => 'index') and return
end
#country_selected = !#user.address.nil? && !#user.address.country.nil? ? #user.address.country : Country.cached_find_by_iso('US')
#states = #country_selected.states.sort_by {|obj| obj.name}
render 'edit'
end
My question is, if there is an error in my form, how do I get the original values that someone entered before submitting the form to remain instead of the values that were previuosly saved? Right now, if an error occurs, all the values the user previously entered are replaced by what was saved before.
Edit:
The solution didn't work. I have a "dob" field (which is a PostGres DATE column), and hwen I entered an invalid value (e.g. "1234") and clicked "Save", everything saved without an error being thrown. Below is my user model. I also added the definition of my date field in my view.
class User < ActiveRecord::Base
has_many :assignments
has_many :roles, through: :assignments
belongs_to :address, :autosave => true #, dependent: :destroy
accepts_nested_attributes_for :address
attr_accessor :dob_string
def dob_string
#dob_string || (self.dob ? self.dob.strftime('%m/%d/%Y') : "")
end
def dob_string=(dob_s)
date = dob_s && !dob_s.empty? ? Date.strptime(dob_s, '%m/%d/%Y') : nil
self.dob = date
rescue ArgumentError
errors.add(:dob, 'The birth date is not in the correct format (MM/DD/YYYY)')
#dob_string = dob_s
end
def role?(role)
roles.any? { |r| r.name.underscore.to_sym == role.to_s.underscore.to_sym }
end
def admin?
role? "Admin"
end
def name
name = first_name
if !first_name.nil?
name = "#{name} "
end
"#{name}#{last_name}"
end
This will happen automatically if you load the attributes into the user slightly differently. the usual way to do it is something more like this:
def update
#user = User.new(user_params)
if #user.save
# do stuff for when it saves correctly
else
# re-display the edit page with render :edit
Note: saving returns false if there are errors, so you don't need to check errors separately from this
But I see you're only letting them update the current user... (which is good). in which case you'll want:
def update
#user = current_user
# applies the attributes without saving to db
#user.assign_attributes(user_params)
if #user.save
# do stuff for when it saves correctly
else
# re-display the edit page with render :edit
Doing either of these will allow the #user object to receive the attributes entered by the user - and that will then be passed to the form and be displayed.
Note: this assumes you are using something like form_for #user in your view (you didn't include that snippet)
I am attempting to add the ability for users to add comments to posts on my rails site.
I have a Posts table and a Users table in my database. I'm using resourceful routes to display individual posts on the 'show' action of my Posts controller. I want to be able to have a comment box show up under the post that the user can enter a comment into, then click submit and have it create the comment.
I tried making a model for comments, giving them a belongs_to relationship for both user and post. I also added the has_many relationship to the user and post models. I then tried to have a Comment Controller use a 'create' action in order to process a form on each posts' 'show' action.
I am running into the issue of not being able to get the post_id to inject into the new comment. I can get the user_id by grabbing it from the user's session, but the only thing passed to the 'create' action on the Comments Controller is the actual text of the comment through the form.
Is this a good way of going about adding this feature? There must be a better way of doing it, or maybe I'm just missing something.
My Posts Controller 'show' action:
#PostsController.rb
def show
#post = Post.where(:id => params[:id]).first
if #post.nil?
flash[:error] = "Post does not exist"
redirect_to(root_path)
end
#comment = Comment.new
end
The form on the 'show' view for the 'show' action on PostsController:
#views/posts/show.html.erb
<%= form_for #comment do |f| %>
<%= f.text_area(:content, :size => '20x10', :class => 'textarea') %>
<%= f.submit('Create Post', class: 'button button-primary') %>
<% end %>
My Comments Controller 'create' action:
#CommentsController.rb
def create
#comment = Comment.new(params.require(:comment).permit(:content, :post_id, :user_id))
#comment.user_id = session[:user_id]
#Need to set post_id here somehow
if #comment.valid?
#comment.save
flash[:success] = "Comment added successfully."
redirect_to(post_path(#comment.post))
else
#error = #comment.errors.full_messages.to_s
#error.delete! '[]'
flash.now[:error] = #error
render('posts/show')
end
end
My Post model:
class Post < ApplicationRecord
belongs_to :subject
belongs_to :user
has_many :comments
validates :title, :presence => true,
:length => {:within => 4..75}
validates :content, :presence => true,
:length => {:within => 20..1000}
end
My Comment model:
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
validates :content, :presence => true,
:length => {:within => 6..200}
end
In your post controller show action, make the new comment belong to the post
def show
#post = Post.where(:id => params[:id]).first
if #post.nil?
flash[:error] = "Post does not exist"
redirect_to(root_path)
end
#comment = #post.comments.new # <--- here's the change
end
Then add the post_id field to the form as a hidden field
<%= form_for #comment do |f| %>
<%= f.hidden_field :post_id %>
<%= f.text_area(:content, :size => '20x10', :class => 'textarea') %>
<%= f.submit('Create Post', class: 'button button-primary') %>
<% end %>
And you should be good to go without changing the comment controller create action
Thanks in advance for your help.
This may get a little long.
Setup
Models:
class Collection < ActiveRecord::Base
has_many :collection_ownerships
has_many :items
has_many :users, :through => :collection_ownerships
validates :title, presence: true, length: { maximum: 25 }
validates :description, length: { maximum: 100 }
end
class Item < ActiveRecord::Base
belongs_to :collection
has_many :item_ownerships, :dependent => :destroy
accepts_nested_attributes_for :item_ownerships
validates :name, :presence => true, length: { maximum: 50 }
validates :collection, :presence => true
end
class ItemOwnership < ActiveRecord::Base
belongs_to :item
belongs_to :user
validates :user, :presence => true
validates :item, :presence => true
end
Controllers
class ItemsController < ApplicationController
before_action :authenticate_user!
before_filter(:except => :toggle_item_owned_state) do
#collection = current_user.collections.find(params[:collection_id])
#item_list = #collection.items
end
def toggle_item_owned_state
#item = Item.find_by_id(params[:item_id])
#io = #item.item_ownerships.find_by_user_id(current_user)
#result = #io.update_attributes(:owned => #io.owned? ? false : true)
respond_to do |format|
if #result
format.html { }
format.js {}
else
format.html { }
format.js {}
end
end
end
def index
end
def new
#item = #collection.items.new
#item_ownership = #item.item_ownerships.build(:owned => true, :user => current_user, :item => #item)
end
def create
#item ||= #collection.items.new(item_params)
#item_ownership ||= #item.item_ownerships.build(:user => current_user, :item => item)
if #item.save
redirect_to collection_items_path(#collection)
else
flash.now[:alert] = "There was a problem saving this item."
render "new"
end
end
def edit
#item = #collection.items.find_by_id(params[:id])
end
def update
#item = #collection.items.find_by_id(params[:id])
if #item.update_attributes(item_params)
redirect_to collection_items_path(#collection)
else
flash.now[:alert] = "There was a problem saving this item."
render "edit"
end
end
def item_params
params.require(:item).permit(:name,
item_ownerships_attributes: [:id, :owned ])
end
end
View
<div class="row">
<span class="col-sm-12">
<div id="add_item">
<%= form_for [#collection, #item] do |f| %>
<div class="form-group <%= 'has-error has-feedback' if #item.errors[:name].present? %>">
<label class="sr-only" for="item_name">Item Name</label>
<%= f.text_field :name, :autofocus => true, :placeholder => "Item Name", :class => "form-control", :'aria-describedBy' => "itemNameBlock" %>
<% if #item.errors[:name].present? %>
<span id="itemNameBlock" class="error">Item <%= #item.errors[:name].first %></span>
<% end %>
<%= f.fields_for :item_ownerships do |io| %>
<%= io.check_box :owned %> Owned
<% end %>
</div>
<div id="signin_button_row">
<%= f.submit "Save", :class => "form-control green_button" %>
<span id="forgot_my_password" class="right-justify">
<%= link_to "cancel", collection_items_path(#collection), :class => "new_colors terms" %>
</span>
</div>
<% end %>
</div>
</span>
</div>
Symptom of problem:
When I submit the form in a failure situation (ie name is not provided), the form currently detects the validation and displays the error, however, I now get two checkboxes called 'owned' appearing in the form. Each failing submission adds an additional checkbox (as per attached image).
Can anyone help?
Update
This issue has taken a turn for the strange. I haven't changed anything (I know, I know, you don't believe it) other than restarting the server but now nothing is saved even with valid data. I am getting validation errors saying:
Validation failed: Item ownerships user can't be blank, Item ownerships item can't be blank
In your controller create action you have
def create
#item ||= #collection.items.new(item_params)
#item_ownership ||= #item.item_ownerships.build(:user => current_user)
if #item.save
redirect_to collection_items_path(#collection)
else
flash.now[:alert] = "There was a problem saving this item."
render "new"
end
end
Problem is on third line when you assign #item_ownership this variable is always empty at this time so code after = is executed. #item.item_ownerships.build(:user => current_user) always build new item_ownership and stores it into #item instance. So after first call of create you will have two item_ownerships first is from form and second is newly created. If validation fails and you send form again you will have three instances of item_ownerships two from the form a one newly created etc.
#edariedl was on the right track in addressing the initial response. The second line is completely unnecessary.
The second issue mentioned in the update I addressed in a separate question found here: Losing mind over validation failure saving nested models
My nested form is not working properly no matter what I try and I searched all the StackExchange's for a solution to this seemingly easy problem. This is where I am right now to get it to work at show up in the view at all.
The form is using the Event controller create action from a non-restful location, hence the global variable (a pages controller, with a specific page, where the form is generated). My ticket model gets generated when the nested form is submitted, and the Event ID gets passed, but it doesn't fill in the "Name" field for the ticket model because it says "Unpermitted Parameters: Ticket." But they're defined as whitelisted in the Events controller! Argh! I'm thinking something is wrong with the form, but nothing I try seems to work.
Any help would be appreciated.
* UPDATED CODE THAT IS NOW WORKING *
Form.html.erb:
<div class="form-inputs">
<%= simple_form_for #event, :html => { :class => 'form-horizontal' } do |f| %>
<div class="row">
<div class="col-xs-6">
<%= f.input :name, class: "control-label" %>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<%= f.simple_fields_for :tickets do |ticket| %>
<%= ticket.input :name %>
<% end %>
</div>
</div>
<div class="form-actions">
<%= f.button :submit, :class => 'btn-primary' %>
<%= link_to t('.cancel', :default => t("helpers.links.cancel")),
launchpad_path, :class => 'btn btn-default' %>
<% end %>
</div>
</div>
Event_Controller.rb
def new (this is totally skipped and unnecessary)
#event = Event.new
#ticket = #event.tickets.build
end
def create
#event = current_user.events.build(event_params)
respond_to do |format|
if #event.save
format.html { redirect_to #event, notice: 'Your event was created.' }
else
format.html { render :new }
end
end
end
def event_params
params.require(:event).permit(:name, tickets_attributes: [ :name, :id, :event_id, :_destroy ])
end
Pages_Controller.rb (where the form originate
def new
#event = Event.new
#ticket = #event.tickets.build
end
Event.rb
class Event < ActiveRecord::Base
# Database Relationships
has_many :tickets, dependent: :destroy
accepts_nested_attributes_for :tickets, :allow_destroy => true
end
Ticket.rb
class Ticket < ActiveRecord::Base
belongs_to :event
end
Routes.rb
resources :events do
resources :tickets
end
As well as the information from Alejandro (which is correct), you also have f.simple_fields_for #ticket, ... whereas you should have f.simple_fields_for :tickets, ...
If you check your log/development.log for the Processing by EventsController#create the line after will be a Parameters: line, you'll see that the parameters that have been sent through are under a :ticket key instead of a :tickets_attributes key because of the fields_for error.
Fix that, and the permit line and you should be fine.
Update
Hopefully you realized that you also don't need the #ticket = #event.tickets.build(event_params[:ticket_attributes]) line at all once that fields_for is fixed too. The setting of all the associated tickets is all done via the Event object thanks to the accepts_nested_attributes_for helper.
Just, remove from create action this line:
#ticket = #event.tickets.build(event_params[:ticket_attributes])
And, change your event_params:
def event_params
params.require(:event).permit(:name, :main_event_image, tickets_attributes: [:id, :name, :cost, :event_id, :registration_id, :created_at])
end
Te field name must be: tickets_attributes: [ ... (tickets in plural). I think this do the trick.
Edit: I'm agree with #smathy, if no fix to f.simple_fields_for :tickets ... it can't work.
Your new method must look like this:
def new
#new_event = Event.new
#new_event.tickets.build
end
I'm a fan of standards, and I prefer use #event instead of #new_event as in your form (it's part of convention over configuration on rails)
I was stuck at the same problem like crazy and at the end I was able to fix it... Try placing the binding.pry in the first line of create method and print the event_params hash and check if you see ticket_attributes hash inside of it ... That's when it ll throw unpermitted parameter ... And I see event has_many tickets , so I am guessing ticket_attributes needs to be pluralized to be tickets_attributes
Hey guys, this has been plaguing me all week long. I am new to Rails, so please be gentle :)
My root problem is I'm trying to write a form_for Post that will use autocompletion on an input to tie the post to a Client. The database is looking for a client_id, not a text name.
So I have tried a custom validation that will lookup the text value and replace it with an id.
My Post.rb file has this:
class Post < ActiveRecord::Base
belongs_to :client
attr_accessor :client_name
attr_accessible :client_name
before_validation_on_create :validate_client
def validate_client
self.client_id = 1 # just a test
end
def client_exists(passed_name)
return Client.where(:full_name => passed_name).first.blank?
end
end
But when I do this, none of the form variables get passed. The database gets all blank entries except for the client_id. How can I accomplish this? Why aren't my form variables being passed in? Many thanks in advance.
Edit 1: added create definition from posts_controller.rb
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html {
redirect_to(posts_url) # redirect_to(#post, :notice => 'Post was successfully created.')
}
format.xml { render :xml => #post, :status => :created, :location => #post }
format.js
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
Edit 2: Thanks to #apneadiving, I changed the attr_accesible to include the other attributes, and that passes the POST entries into the db.
attr_accessible :client_id, :time, :content, :client_name
But when I change the validate_client function to search for the client_id,
def validate_client
passed_name = self.client_name
if client_exists(passed_name)
c = Client.where(:full_name => passed_name).first
self.client_id = c.id
else
errors.add_to_base "Error"
end
end
It always gives me this error:
Called id for nil, which would
mistakenly be 4 -- if you really
wanted the id of nil, use object_id
Edit 3: Here's my post form. I can't get the value of :client_name properly.
<%= form_for(#post) do |f| %>
<%= render 'common/error_messages', :object => f.object %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_area :content, :size => "50x6" %>
</div>
<div class="field">
<div class="sub-field">
<%= f.label :client_name %><br />
<%= f.text_field :client_name, :size => "26" %>
</div>
<div class="sub-field">
<%= f.label :date %><br />
<%= f.text_field(:time, :class => 'date', :size => "10", :value => Date.today.strftime('%m/%d/%Y')) %>
</div>
<div class="sub-field">
<%= f.label :time %><br />
<%= f.time_select :time %>
</div>
</div>
<div class="actions clear">
<%= f.submit %>
</div>
<% end %>
Edit 4: The solution. I was struggling to get the :client_name due to my text being too far tabbed in (I was originally privatizing the function and then took the word "private" out). A modified version of #apneadiving's answer solved it for me!
class Post < ActiveRecord::Base
belongs_to :client
attr_accessor :client_name
validate :validate_client
def validate_client
passed_name = self.client_name
unless client = Client.find_by_full_name(passed_name).blank?
self.client_id = Client.find_by_full_name(passed_name).id
else
errors.add(:client_name, "'#{passed_name}' cannot be found.")
end
end
end
Beware of your attr_acessible:
if you set one, you have to set all the variables that can be set through params. Otherwise they are protected against mass assignment
EDIT 1:
seems c.id is nil which means your client_exists function doesn't work as expected. Try the following (not tested):
def validate_client
unless client = Client.find_by_full_name(client_name).nil?
client_id = client.id
else
errors.add_to_base "Error"
end
end
Your client_exists code is wrong, it returns true if it doesn't exist.
Also, since your client_exists and the validation reuse the same query i would rewrite that a bit.
def validate_client
passed_name = self.client_name
existing_client = Client.where(:full_name => passed_name).first
if existing_client
self.client_id = existing_client.id
else
errors.add_to_base "Error"
end
end