Non Mass assignable field in view - ruby-on-rails

Iam kinda stuck at this problem. Trying to update a non mass-assignable attribute in the job model from an admin view. I need a button which trigger an action method in the admin controller which just changes job's isactive value to true.
In admin controller:
def approve
#job.update_attribute(:isactive = true)
if #job.save
redirect_to(:action => :show)
flash[:notice] = "Job Approved"
else
render 'show'
end
end
In jobs/1 view
<div style="text-align:center;" class="widget">
<%= form_for(#job, :url => url_for(:controller => 'admin', :action => 'approve'), :html => {:id => "contact_form", :class => "g-form"}) do |f| %>
<%= button_tag( :class => "g-btn type_default") do %>
<i class="icon-share-alt icon-white"></i>Approve this job
<% end %>
<% end %>
<% end %>
No matter what i do this form gets submitted to the job update action and show job successfully updated rather than the notice "Job approved" from the approve action in admin.
I have added the following route as well. but not sure what iam doing wrong. I need a simple button (ideally a block because of the styling req) which sets job's isactive field to true.
match "jobs/:id", :via=>:post, :controller=>"admin", :action=>"approve"

Mass assignment was designed to protect precisely against what you're trying to do here so ideally speaking, you should add it to a whitelist (attr_accessible) if you need to modify it.
However, there are a few issues with your controller action that will need to be addressed even if you get the isactive attribute whitelisted.
#job.update_attribute(:isactive = true) is not valid. update_attribute does not handle assignments, the correct syntax is update_attribute(attribute, value) so it should be:
#job.update_attribute(:isactive, true)
Also, the update_attribute method calls save on the passed object. Calling #job.save after update_attribute (provided there is no other code assigning attributes) is redundant.
A better way to write this method would be:
def approve
if #job.update_attribute(:isactive, true)
redirect_to(:action => :show)
flash[:notice] = "Job Approved"
else
render 'show'
end
end
However, if you cannot modify the model and are not worried about callbacks or validation, you can update the database column directly with update_column (or update_columns in Rails 4):
def approve
if #job.update_column(:isactive, true)
redirect_to(:action => :show)
flash[:notice] = "Job Approved"
else
render 'show'
end
end

Related

rendering the partials in controller after the validation check

I have two partial views for two different sign up forms. On my home page , based on the link one clicks on, I'm rendering respective form.(views/application/index)
= link_to 'Mentor', new_user_path(user_role: true), :class =>'btn'
= link_to 'Mentee', new_user_path, :class =>'btn'
In views/users/new.html.haml , I'm checking the user role and redirecting to the respective form.
- if params[:user_role]
= render 'mentor'
- else
= render 'mentee'
In the user model I've added validation like this.
class User < ActiveRecord::Base
email_regex = /\A[\w+\-.]+#cisco.com/i
validates :cisco_email, :presence => true,
:format => { :with => email_regex,}
validates :work_city, :presence => true
end
So, when there is any invalid field I want to direct to the same form with a flash message. My controller looks like this.
class UsersController < ApplicationController
def index
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(params[:user]) # Not the final implementation!
if #user.save
flash[:success] = "Welcome to the CSG Mentoring Tool!"
redirect_to #user
else
flash[:notice] = "Error regsitering."
if params[:user][:user_role]
render :partial => 'users/mentor'
else
render :partial => 'users/mentee'
end
end
end
end
When an invalid field entry is there, it is redirecting to 'mentee' page no matter on which page the error is made. Also the entire css styling gets changed and flash is also not displayed
Why this is not working?
if params[:user][:user_role]
render :partial => 'users/mentor'
else
render :partial => 'users/mentee'
end
params[:user][:user_role] is nil.
You can check it using lots of way:
Above your if condition raise params[:user].inspect
Why its nil?
Reason of this is You are passing new_user_path(user_role: true) user_role true, but user_role is not true in mentor form.
params[:user_role] will not set user_role = true field in mentor form.
Set user_role
<%=f.hidden_field :user_role, value: params[:user_role] %>
If its supposed to be true for mentor always
<%=f.hidden_field :user_role, value: true %>
By default flash will make them available to the next request, but sometimes you may want to access those values in the same request.
Reference
This works with redirection
flash[:success] = "Welcome to the CSG Mentoring Tool!"
This will work with render
flash.now[:success] = "Welcome to the CSG Mentoring Tool!"

How do I create friends?

I can't seem to get Amistad friendships to work correctly. I am getting the following error:
ActiveRecord::RecordNotFound in FriendshipsController#update
Couldn't find Friendship with id=29
I am also using devise and cancan. I followed the gem setup on the wiki pages and created my controller as described in this related post.
class FriendshipsController < ApplicationController
before_filter :authenticate_user!
def index
#friends = current_user.friends
#pending_invited_by = current_user.pending_invited_by
#pending_invited = current_user.pending_invited
end
def create
#friend = User.find(params[:user_id])
#friendship_created = current_user.invite(#friend)
if #friendship_created
redirect_to users_path, :notice => "Your friend request is pending"
end
end
def update
#friend = User.find(params[:user_id])
#friends = current_user.friends
#pending_invited_by = current_user.pending_invited_by
redirect_to users_path, :notice => "You are now friends!"
end
def destroy
#friend = User.find(params[:user_id])
#friendship = current_user.send(:find_any_friendship_with, #friend)
if #friendship
#friendship.delete
#removed = true
redirect_to users_path, :notice => "You are no longer friends!"
end
end
def createblock
#friend = User.find(params[:user_id])
current_user.block #friend
redirect_to users_path, :notice => "You have blocked #{#friend.first_name}"
end
end
I loop though my users in the following manner checking the current status of the user and offering appropriate actions.
<% if current_user.friend_with? user %>
<%= link_to "Unfriend", friend_path(user), :method => "delete", :class => 'btn btn-mini' %>
<% elsif current_user.invited? user %>
<span class="btn btn-mini disabled">Pending</span>
<% elsif user.invited? current_user %>
<%= link_to "Accept", friend_path(user), :method => "put", :class => 'request-approve btn btn-mini' %>
<%= link_to "Decline", friend_path(user), :method => "delete", :class => 'request-decline btn btn-mini' %>
<% else %>
<%= link_to "Add friend", friends_path(:user_id => user), :method => "post", :class => 'btn btn-mini' %>
<% end %>
Figured it would be useful to see what the friendships table looks like in my schema:
create_table "friendships", :force => true do |t|
t.integer "friendable_id"
t.integer "friend_id"
t.integer "blocker_id"
t.boolean "pending", :default => true
end
add_index "friendships", ["friendable_id", "friend_id"], :name => "index_friendships_on_friendable_id_and_friend_id", :unique => true
I understand the error just cannot figure out how this should change. I think my issue is that I am passing in a friend id and it is expecting a friendship id. My only problem with this solution is that every example or post I can find suggests passing user_id, like this post above where the answerer states the gem developer supplied the code he answers with.
What I feel like I need in my update method is to replace:
#friend = User.find(params[:id])
With this:
#friendship = Friendship.find_by_friend_id(params[:id])
EDIT
I can successfully request a friend, I just cannot accept or decline a friend. I a listing of users, clicking the "Add Friend" link creates the record in the friendships db correctly. If I log ins as that recently requested user and attempt to accept the request is when I get the above error. This also occurs if I attempt to decline the request.
The friends method you asked to see come with the amistad gem, here is the code for that method. As for my Ruby logs the section that displays the error was very long, so I have included it in this gist.
Given my current reputation, I can only post an answer instead of a comment to your question but as far as I can see from the controller sources you posted, you are not calling current_user.approve #friend in your update action.
I used this gem in one of my projects recently without running into any problems. The controller actions look like this:
def update
#friend = User.find_by_id(params[:id])
if current_user.approve #friend
redirect_to friendships_path, notice: t('.confirmation_successful')
else
redirect_to friendships_path, alert: t('.confirmation_unsuccessful')
end
end
def destroy
#friend = User.find_by_id(params[:id])
if current_user.remove_friendship #friend
redirect_to friendships_path, notice: t('.deletion_successful')
else
redirect_to friendships_path, alert: t('.deletion_unsuccessful')
end
end
I hope this helps.
The lookup problem is because you're passing ids inconsistently. In 3 of the links, you're passing the User object directly, which should automatically store the id in params[:id], which you can use in your action as User.find(params[:id]). But in your actions, you're extracting it from params[:user_id], which is empty. Not sure how you're getting an ID of 29 in your error message (or 32 or whatever), but...
If you change all your actions to expect params[:id] and switch the "Add friend" path link to pass in a User object the way the others already are, you should be passing the right data in the right parameter, and the lookup should straighten itself out.
Of course, as Wonky Business points out, you're not actually calling approve in your update method, so nothing will actually link, but at least you should be finding all your model objects.
As an aside, it appears from your paths you're remapping the friendship named routes to friend instead. That's muddling the issue because none of the RESTful routes are actually doing what their noun/verb combination implies: if you call friends_path(user) with a POST, there should be a new Friend object when you're done, but this controller is creating and destroying Friendship objects and leaving the Friend objects alone.
If you delete that alias and switch to friendship_path and so forth, then REST will actually be doing what it says it is: managing friendship objects.
Hope that helps!

In Rails when a resource create action fails and calls render :new, why must the URL change to the resource's index url?

I have a resource called Books. It's listed as a resource properly in my routes file.
I have a new action, which gives the new view the standard:
#book = Book.new
On the model, there are some attributes which are validated by presence, so if a save action fails, errors will be generated.
In my controller:
#book = Book.create
... # some logic
if #book.save
redirect_to(#book)
else
render :new
end
This is pretty standard; and the rationale for using render:new is so that the object is passed back to the view and errors can be reported, form entries re-filled, etc.
This works, except every time I'm sent back to the form (via render :new), my errors show up, but my URL is the INDEX URL, which is
/books
Rather than
/books/new
Which is where I started out in the first place. I have seen several others posts about this problem, but no answers. At a minimum, one would assume it would land you at /books/create, which I also have a view file for (identical to new in this case).
I can do this:
# if the book isn't saved then
flash[:error] = "Errors!"
redirect_to new_book_path
But then the #book data is lost, along with the error messages, which is the entire point of having the form and the actions, etc.
Why is render :new landing me at /books, my index action, when normally that URL calls the INDEX method, which lists all the books?
It actually is sending you to the create path. It's in the create action, the path for which is /books, using HTTP method POST. This looks the same as the index path /books, but the index path is using HTTP method GET. The rails routing code takes the method into account when determining which action to call. After validation fails, you're still in the create action, but you're rendering the new view. It's a bit confusing, but a line like render :new doesn't actually invoke the new action at all; it's still running the create action and it tells Rails to render the new view.
I just started with the Rails-Tutorial and had the same problem.
The solution is just simple: If you want the same URL after submitting a form (with errors), just combine the new and create action in one action.
Here is the part of my code, which makes this possible (hope it helps someone^^)
routes.rb (Adding the post-route for new-action):
...
resources :books
post "books/new"
...
Controller:
...
def create
#book = Book.new(book_params)
if #book.save
# save was successful
print "Book saved!"
else
# If we have errors render the form again
render 'new'
end
end
def new
if book_params
# If data submitted already by the form we call the create method
create
return
end
#book = Book.new
render 'new' # call it explicit
end
private
def book_params
if params[:book].nil? || params[:book].empty?
return false
else
return params.require(:book).permit(:title, :isbn, :price)
end
end
new.html.erb:
<%= form_for #book, :url => {:action => :new} do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :isbn %>
<%= f.text_field :isbn %>
<%= f.label :price %>
<%= f.password_field :price %>
<%= f.submit "Save book" %>
<% end %>
Just had the very same question, so maybe this might help somebody someday. You basically have to make 3 adjustments in order for this thing to work, although my solution is still not ideal.
1) In the create action:
if #book.save
redirect_to(#book)
else
flash[:book] = #book
redirect_to new_book_path
end
2) In the new action:
#book = flash[:book] ? Book.new(flash[:book]): Book.new
3) Wherever you parse the flash hash, be sure to filter out flash[:book].
--> correct URL is displayed, Form data is preserved. Still, I somehow don't like putting the user object into the flash hash, I don't think that's it's purpose. Does anyboy know a better place to put it in?
It doesn't land you at /books/new since you are creating resource by posting to /books/. When your create fails it is just rendering the new action, not redirecting you to the new action. As #MrYoshiji says above you can try redirecting it to the new action, but this is really inefficient as you would be creating another HTTP request and round trip to the server, only to change the url. At that point if it matters you could probably use javascript change it.
It can be fixed by using same url but different methods for new and create action.
In the routes file following code can be used.
resources :books do
get :common_path_string, on: :collection, action: :new
post :common_path_string, on: :collection, action: :create
end
Now you new page will render at url
books/common_path_string
In case any errors comes after validation, still the url will be same.
Also in the form instead using
books_path
use
url: common_path_string_books_path, method: :post
Choose common_path_string of your liking.
If client side validation fails you can still have all the field inputs when the new view is rendered. Along with server side errors output to the client. On re-submission it will still run create action.
In books_controller.rb
def new
#book = current_user.books.build
end
def create
# you will need to have book_params in private
#book = current_user.books.build(book_params)
if #book.save
redirect_to edit_book_path(#book), notice: "Book has been added successfully"
# render edit but you can redirect to dashboard path or root path
else
redirect_to request.referrer, flash: { error: #book.errors.full_messages.join(", ") }
end
end
In new.html.erb
<%= form_for #book, html: {class: 'some-class'} do |f| %>
...
# Book fields
# Can also customize client side validation by adding novalidate:true,
# and make your own JS validations with error outputs for each field.
# In the form or use browser default validation.
<% end %>

Ruby on Rails - Checking if Submitted Form Data matches Database data

I have a form in which a user ('member') submits a 4-digit pin, which is then saved to a session variable called :submitted_pin. For some reason, the quick if/else statement isn't working properly, and I am assuming it is a silly mistake on my part, but if your willing to take a look I would really appreciate it!
View
<%= form_for :member_pin, :url => { :action => "verify_pin", :id => member.id } do |f| %>`
<%= f.label :pin %>
<%= f.text_field :pin %>
<%= f.submit 'Submit' %>
Controller
Before Filter
before_filter :validate_pin, :only => :show
Action (POST Route)
def verify_pin
#member = Member.find(params[:id])
session[:submitted_pin] = params[:member_pin][:pin]
redirect_to #member
end
Filter
def validate_pin
#member = Member.find(params[:id])
#member_pin = #member.pin
if session[:submitted_pin] == #member_pin
render #member
else
redirect_to '/'
end
end
And the result of all of this is a redirect to my root_url no matter what, even if the Pin entered does match the pin in the database for that user. Help! Thanks :)
Based on the results of the raise we talked about in the comments, it sounds like there's a class mismatch. The easiest way to fix (and, IMHO, the easiest to read) would be to adjust your validator code as follows:
if session[:submitted_pin].to_i == #member_pin.to_i
render #member
else
redirect_to '/'
end
If there's any chance that session[:submitted_pin] would be nil, you can use session[:submitted_pin].try(:to_i); that'll return nil if the variable is not set and prevent an error from getting thrown.

Rails validation over redirect

I'm trying out the beast forum written in rails and will use this as an example of a problem I keep facing.
The forum has a topics/show action and view with a form at the bottom to create a new post within the topic.
Submitting the form goes to posts/create and if the validation passes redirects back to topics/show and works fine, however if the validation fails (leaving out the body field) you're redirected to the same topics/show and back to the form, with no validation errors... normally if validation fails you're left on whatever/create with render :action => new.
Are the validations being lost in the redirect, and what's the best method of getting it working?
See code below:
PostsController.rb
def create
#post = current_user.reply #topic, params[:post][:body]
respond_to do |format|
if #post.new_record?
format.html { redirect_to forum_topic_path(#forum, #topic) }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
else
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to(forum_topic_post_path(#forum, #topic, #post, :anchor => dom_id(#post))) }
format.xml { render :xml => #post, :status => :created, :location => forum_topic_post_url(#forum, #topic, #post) }
end
end
end
TopicsController.rb
def show
respond_to do |format|
format.html do
if logged_in?
current_user.seen!
(session[:topics] ||= {})[#topic.id] = Time.now.utc
end
#topic.hit! unless logged_in? && #topic.user_id == current_user.id
#posts = #topic.posts.paginate :page => current_page
#post = Post.new
end
format.xml { render :xml => #topic }
end
end
topics/show view
<% form_for :post, :url => forum_topic_posts_path(#forum, #topic, :page => #topic.last_page) do |f| %>
<%= f.error_messages %>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td rowspan="2" width="70%">
<%= f.text_area :body, :rows => 8 %>
</td>
<td valign="top">
<%= render :partial => "posts/formatting" %>
</td>
</tr>
<tr>
<td valign="bottom" style="padding-bottom:15px;">
<%= submit_tag I18n.t('txt.views_topics.save_reply', :default => 'Save reply') %>
</td>
</tr>
</table>
<% end %>
Many thanks.
I think you have two problems here.
Keeping validation errors through a redirect
Repopulating the form fields so the user doesn't have to enter all the information again.
Both of these things are connected.
Validation errors are usually displayed through the error_msg_for method which acts on an object. Usually provided by the controller as the an instance variable of object that could not be saved. That same instance variable is used to repopulate the form.
During a redirect the controller will usually instantiate an instance variable using the params hash. So any information used to determine why a save failed is lost. Normal resources will render on save failure and redirect on success, this causes two things happen.
The instance of the object is passed to error_msg_for creating that nice uniform error box.
The instance of the object is used to populate the fields of the form, allowing your user to edit only what is necessary.
I don't know Beast so well, so I'm not sure if the form to create threads is an active record model. But the following will give you an idea how to work around your problem. It would involve modifying your local copy of the Beast plugin, so if you're using a tool to keep it updated, your changes might get lost.
I've been using these following methods to get your validation problems. Assuming the form you're talking about is based on a nmodel they should provide you with everything you need to repopulate a form.
Essentially you store a shallow copy of the object with the errors in the flash hash, using clone_with_errors. You have to use a shallow copy or else you'll run into problems when displaying errors for records with multiple associations.
Then I use my_error_msg_for which takes the same options as the standard error_msg_for to build the error messages html. I only wrote it because for some reason the standard error_msg_for method didn't work with objects stored in the hash. It's almost identical to the official source version of error_msg_for which was troubling.
/app/controllers/examples_controller.rb
class ExamplesController < ApplicationController
def update
...
if #example.save
regular action
else
flash[:errors] = clone_with_errors(#example)
respond_to do |format|
format.html redirect_to(#example)
end
end
end
/app/views/examples/show.html.erb
<div id="error">
<% if flash[:errors] && !flash[:errors].empty? then -%>
<p ><%= my_error_msg_for flash[:errors] %></p>
<% end -%>
</div>
...
Here's the code you need to make it all work.
application_controller.rb
def clone_with_errors(object)
clone = object.clone
object.errors.each{|field,msg| clone.errors.add_to_base(msg)}
return clone
end
application_helper.rb
def _error_msg(*params)
options = params.extract_options!.symbolize_keys
if object = options.delete(:object)
objects = [object].flatten
else
objects = params.collect {|object_name| instance_variable_get("##{object_name}") }.compact
end
count = objects.inject(0) {|sum, this| sum + this.errors.count }
unless count.zero?
html = {}
[:id, :class].each do |key|
if options.include?(key)
value = options[key]
html[key] = value unless value.blank?
else
html[key] = 'errorExplanation'
end
end
options[:object_name] ||= params.first
options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message) && !options[:header_messag].nil?
options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message) && !options[:message].nil?
error_messages = objects.sum {|this| this.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
contents = ''
contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank?
contents << content_tag(:p, options[:message]) unless options[:message].blank?
contents << content_tag(:ul, error_messages)
content_tag(:div, contents, html)
else
''
end
end
def my_error_msg_for(params)
_error_msg_test :object_name => params[:object].class.name.gsub(/([a-z])([A-Z])/,'\1 \2').gsub(/_/, " "),
:object => params[:object], :header_message => params[:header_message], :message => params[:message]
end
I'm afraid I don't know anything about Beast, but speaking generically, everything is lost when you redirect. It's a new page request, and everything is reset unless it's been stored somewhere (the database or the session, normally.)
The normal flow you tend to see with forms is to redirect if the object is saved, but to render if the save fails. The view file can then pick up whatever variables have been set in the controller - which would normally include the object that didn't save and its validation messages.
Sorry that doesn't solve your problem, but hopefully it may give you some clues.
My answer to a very similar question posted more recently here on StackOverflow covers a number of pros and cons to the redirect_to vs. render debate. I'd love to hear if anyone has any other pros/cons to add to the discussion.

Resources