ActiveModel::MassAssignmentSecurity::Error (backbone.js + rails + rails-backbone gem) - ruby-on-rails

I am trying to continue along the path I was lead down in this post: render show page on submit backbone.js + rails + rails-backbone gem
I am still trying to figure out what I describe in the first paragraph of that question. I was messing around in the firebug console. When I click submit from the root page everything works fine (except the app doesn't navigate to the newly created objects show page). The url changes to http://localhost:3000/#/x (where x is the id of the newly created object). When I click submit again, I get "500 Internal Server Error 22ms". This error comes in the Console > All window with a little red arrow next to it that (when clicked) drops down another section - upon with I click the "HTML" tab - and I get this error:
ActiveModel::MassAssignmentSecurity::Error in UsersController#update
Can't mass-assign protected attributes: created_at, id, updated_at
I think I may have located the issue for this error - I just don't know what to do to fix it. What I think is going on is that I have two different controllers (a home controller, and a users controller) - and both of their index actions define the same thing, here is my users controller index action:
class UsersController < ApplicationController
# GET /users
# GET /users.json
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #users }
end
end
...
end
Here is my home controller index action:
class HomeController < ApplicationController
def index
#users = User.all
end
end
So as you can see, what I think is going on is that when I click submit the first time (from the home page) #users gets set, and the app gets navigated to the new user's show page (although it doesn't which again - ties this all back to my first question - the url changes, but the content of the page does not, the link for my first question is above).
After the url changes to the show page url (without loading the content), I tried submitting another user form with unique input and I get the mass-assign error above. This seems to be because #users has already been set by the home controller...so that when it is called again from the users controller (upon the second submit) it can't be assigned.
How would I fix this? I need both the home controller and the users controller to be able to use #users...I think. And, tying everything back to to my first question here: render show page on submit backbone.js + rails + rails-backbone gem
Can I do something from within the home controller that redirects the newly created user to the show page (maybe in the create, or update method or users controller/or index method of home controller)? Might this solve the issue I have with redirection to the show page from the root page? All the relevant backbone.js (and my root index.html.erb file) code is posted in my first question...please refer to it as it will fill you in on all the backbone details, and what I am really trying to do.
THANKS!
UPDATE
Here is the update action in my users controller (where the error is supposedly coming from):
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

You are not supposed to send created_at and updated_at attributes to server.
You should inspect parameters you are sending and make sure you are not sending these.
By the way, did my other answer help you?

You should not insert or add created_at, id, updated_at these are generated automatically .......

Related

Rails: practice of rendering new on invalid entry

While reading other peoples controllers code and some other resources, I met this kind of code a lot:
def create
if #record.save
redirect_to #record
else
respond_to do |format|
format.html {render :new}
format.json {render #record.errors}
end
end
But now, just clicking my app pages for fun (this was not covered by rout testing at all), I found out - if you click submit on invalid form, you will have form rendered and current path as /records. But if you hit reload page, this leads to an error:
No route matches GET /records
In my case, I don't have index action, or you will be redirected to index page, which is also rather inconvenient.
Is there any other way of being redirected back to /new but without loosing form inputs or error messages, as it happens with render?
Can I do something like this?
redirect_to new_record_path
respond_to do |format|
format.json {render #record}
end
render loads a view without calling another action. The data from the action containing the render call is used to render that view. That is why the form data can be displayed in that view.
redirect_to starts a new request that calls the action associated with the route. Since a new action is called, the data from the previously called action is not available to the view that the client is directed to.

Conditional routing for nested resource in Rails controller #edit action, depending on where request came from

I have a Foo resource that has_many Bars. I'm using nested resources for a limited number of actions, but otherwise prefer to keep my routing for bars shallow. There are two ways to navigate to the edit view for the Bar object - either from the nested path that includes foo, or from the shallower bar path that isn't nested inside foo. For example, a user might click the edit button from the page at /foos/[:foo_id]/bar/[:bar_id]; or from /bars/[:bar_id].
In the first case, I want the controller to redirect the user back to the parent foo page: /foos/[:foo_id] after the record is updated. In the second case, I want it to redirect to the index view for bars: /bars. I believe I need some sort of conditional in the #edit action in the bars controller that will tell Rails where to go after #update executes.
# config/routes.rb
resources :foos do
resources :bars, only: [:new, :edit]
end
resources :bars
# bin/rake routes:
foo_bars POST /foos/:foo_id/bars(.:format) bars#create
new_foo_bar GET /foos/:foo_id/bars/new(.:format) bars#new
edit_foo_bar GET /foos/:foo_id/bars/:id/edit(.:format) bars#edit
bars GET /bars(.:format) bars#index
POST /bars(.:format) bars#create
new_bar GET /bars/new(.:format) bars#new
edit_bar GET /bars/:id/edit(.:format) bars#edit
bar GET /bars/:id(.:format) bars#show
PATCH /bars/:id(.:format) bars#update
PUT /bars/:id(.:format) bars#update
DELETE /bars/:id(.:format) bars#destroy
The controller for bars:
# app/controllers/bar_controller.rb
def edit
#bar = bar.find(params[:id])
#foo = #bar.foo
end
def update
#bar = bar.find(params[:id])
#foo = #bar.foo
respond_to do |format|
if #bar.update_attributes(bar_params)
format.html { redirect_to #foo, notice: "bar successfully updated" }
else
format.html { render action: "edit" }
end
end
end
I'm trying to change the redirect_to #foo line in the #update action so there is conditional logic that switches out #foo for #bars depending on where the #edit action was initiated. I've tried something like the following to test whether params[:foo] is present when the #edit action is called, setting an instance variable for the redirect.
def edit
if params[:foo]
#redirect_page = #foo
else
#redirect_page = #bars
end
#bar = bar.find(params[:id])
#foo = #bar.foo
end
def update
# code omitted...
format.html { redirect_to #redirect_page, notice: "bar successfully updated" }
# code omitted...
end
This doesn't work. Rails states cannot redirect to nil!. I've also tried something using a test based on URI(request.referer).path in the #edit action, without success.
I'm still not entirely clear how the Rails magic happens in the controller. I believe the #edit action is the proper place to define the conditional for the redirect (or through a method called in the #edit action), as that's where the controller will "see" the incoming request and know where it came from. But I can't quite figure out to capture that information, and pass it along to #update. Appreciate any guidance.
In your edit forms, add a hidden_field_tag:
<%= hidden_field_tag "route", request.env['PATH_INFO'] %>
Then in your controller, you can have an if statement and use a redirect_to based on what the params[:route] is.
I figured it out. The params[:route] method using request.env['PATH_INFO] wasn't working for me, because the 'PATH_INFO' variable in the form was providing the path handed off to the bars#update action, instead of the path where the bars#edit action was initiated.
After clicking "Edit" from the parent foo page at /foos/[:id] the params hash is:
>> params
=> {"controller"=>"bars", "action"=>"edit", "foo_id"=>"3786", "id"=>"16"}
There is no value for params[:route] when the form is first accessed - the hidden field is only added to the params hash after clicking "Update" in the edit form:
>> params[:route]
=> "/foos/3786/bars/16/edit"
This could work, but would require building logic to parse the route in order to redirect to /foos/[:foo_id]
It turned out to be simpler to use the Rails flash method to store the path for redirecting back to the source page. I did this by calling a custom method set_redirect_path in the BarsController, and calling it in bars#edit. This sets a value for the source in the flash, which is available in bars#update. Maybe there's a better/more conventional way to achieve this, but this seems to be a clean and simple way to do what I want.
# app/controllers/bars_controller.rb
def edit
set_redirect_path
#bar = bar.find(params[:id])
#foo = #bar.foo
end
def update
#bar = bar.find(params[:id])
#foo = #bar.foo
respond_to do |format|
if #bar.update_attributes(bar_params)
format.html { redirect_to flash[:source], notice: "bar successfully updated" }
format.xml { head :ok }
else
format.html { render action: "edit" }
format.xml { render xml: #bar.errors, status: :unprocessable_entity }
end
end
end
private
def set_redirect_path
flash[:source] = URI(request.referer).path
end
One advantage of this approach is I can now get rid of conditional logic in the shared partial app/views/bars/_list.html.haml that was required to determine whether clicking the "Edit" button should route to edit_foo_bar_path or to edit_bar_path (i.e. the former is chosen if #foo exists). Consequently, I can delete :edit for the nested resource :bars. Since the flash captures the incoming source of the request and stores it for reference in the #update action, all edit requests can use the same edit_bar_path, regardless of where they originate from. After update Rails redirects the user to the point where they initiated the #edit action.

Why is my Rails app redirecting to `data:,`?

So when I save a record in my Rails 4 app this happens. Here's some details:
I'm using the Ace editor.
The data attribute is no where in my model or app.
The form is a standard form_for (not remote).
The record does save successfully but then it redirects to this weird ass URL.
The code for the update is standard scaffold boilerplate.
# PATCH/PUT /pages/1
# PATCH/PUT /pages/1.json
def update
respond_to do |format|
if #page.update(page_params)
format.html { redirect_to #page, notice: 'Page was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #page.errors, status: :unprocessable_entity }
end
end
end
Anyone have any ideas? Probably something simple but I can't for the life of me figure this one out. Let me know if there's any other pertinent information I can share.
In your specific case (the one shown in your quickcast), Chrome is considering this a security risk because you're submitting a <script> element containing javascript that's being inserted into the renderable contents of the page using [Rails' built-in] asynchronous javascript.
To avoid this, you could:
Strip out the wrapping <script> tags using client-side logic before submitting the form, and then add them back in on the server before saving the record.
Disable Rails' built-in ajaxification of the update action in this controller, so that it submits through plain old HTML
Add an intermediary redirect page between form submittal and viewing the show action
I believe it is because your #page show view is rendering escaped HTML and Javascript. Chrome probably has heuristics to analyze the page and determine what type of document it is. Since it likely doesn't start with <html>, then Chrome assumes it is a data file with the data: protocol. Try rendering to a string and printing the results on the console:
http://guides.rubyonrails.org/layouts_and_rendering.html#using-render
puts render_to_string #page
See section 4.1.1 http://guides.rubyonrails.org/security.html#redirection
data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K
Please update your answer with the show view template, the show action, and the log from render_to_string.

Are redirect_to and render exchangeable?

For the code below, what happens if replacing redirect_to with render or vise verse?
def create
#product = Product.new(params[:product])
respond_to do |format|
if #product.save
format.html { redirect_to(#product, :notice => 'Product was successfully created.') }
else
format.html { render :action => "new" }
end
end
end
It seems OK replacing one with the other in code above. Is there a place where only redirect_to or render has to be used? Render does nothing but rendering a view. Redirect_to sends 302 request to server and current parameters are lost after redirecting.
Thanks.
If you're using render, when the user refreshes the page, it will submit the previous POST request again. This may cause undesired results like duplicate purchase and others.
But if you're using redirect_to, when the user refreshes the page, it will just request that same page again. This is also known as the Post/Redirect/Get (PRG) pattern.
So the place where redirect_to should be used is when you're doing a HTTP POST request and you don't want the user to resubmit the request when it's done (which may cause duplicate items and other problems).
In Rails, when a model fails to be saved, render is used to redisplay the form with the same entries that was filled previously. This is simpler because if you use redirect, you'll have to pass the form entries either using parameters or session. The side effect is that if you refresh the browser, it will try to resubmit the previous form entries. This is acceptable because it will probably fail the same way, or if it's successful now, it was what the user should expect in the first place anyway.
For more in depth explanation about render and redirect, you should read this article.
When you redirect you will generate a new request that hits a controller method, render just renders the associated view. You use render in the create because you want to keep the state of the model object if the save fails so that you can render info about its errors. If you tried to redirect to the new_product path you would create a new model object and loose all the form data the user entered and any errors etc etc
EDIT (with some more info):
An example of a situation where you MUST use redirect_to is if your view template uses instance variables that are not initialized in the controller method you are redirecting from. So you probably could not call render {:action => 'index'} in your create method because the index template probably makes use of a #products variable but your only initialized #product so it would cause an exception
Here is a complete list of what the two methods do that I follow:
1) redirect_to will issue an HTTP 302 status code by default. A 302 redirect is a temporary change and redirects users and search engines to the desired page for a limited amount of time until it is removed. You can optionally specifiy a 301 status code to redirect_to. A 301 status code is used when any page has been permanently moved to another location. Users will now see the new page as it has replaced the old page. This will change the URL of the page when it shows in search engine results.
2) redirect_to will issue a new HTTP request, since it is redirects to a different controller action or URL. You should not make the browser need to make a fresh call unless you really have to, so always question when you are using redirect_to and if it is the right thing, or perhaps a render would be better.
- redirect_to will cause any automatic template rendering of the current action to be skipped.
3) render will issue an HTTP 200 status code by default ( but with an invalid ActiveRecord object, you may want to change this to 422 unprocessable entity). The HTTP 200 OK success status response code indicates that the request has succeeded. The 422 (Unprocessable Entity) status code means the server understands the content type of the request entity nd the syntax of the request entity is correct but was unable to process the contained instructions.
4) render will render a template and any instance variables defined in the controller action will be available in the template. Of course, instance variables will not be available if the subsequent action that redirect_to invokes. IMPORTANT POINT: Redirect hits the controller while Render does not, so if you render a different template, it will not hit the action associated with that template and so those instance variables will not be available!
5) With render, use flash.now, instead of the normal flash.
flash.now[:error] = "There was a problem"
# not
flash[:error] = "There was a problem"
6) If you don't, then the flash message may not show up on the page that's rendered, and it will show up on the next page that's visited.
7) render will not cause the current action to stop executing! redirect_to will not cause the current action to stop executing! You need to invoke 'return' if you need to bypass further execution of code in the action! In the below code, there is an explicit render at the bottom and so you must do a return to avoid an error of redirect and render both being present:
def update
#record = Record.new(record_params)
if #record.save
flash[:success] = "record was successfully saved"
redirect_to records_path
return
end
flash.now[:error] = "please fix the problems in the record"
render :edit
end
Another option:
def update
#record = Record.new(record_params)
if #record.save
flash[:success] = "record was successfully saved"
redirect_to records_path
else
flash.now[:error] = "please fix the problems in the record"
render :edit
end
end
8) The flash message provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed to the very next action and then cleared out. This is a great way of doing notices and alerts:
class PostsController < ActionController::Base
def create
# save post
flash[:notice] = "Post successfully created"
redirect_to #post
end
def show
# doesn't need to assign the flash notice to the template, that's done automatically
end
end
show.html.erb
<% if flash[:notice] %>
<div class="notice"><%= flash[:notice] %></div>
<% end %>
Since you can have both notices and alerts in the flash, you can display both notices and alerts this way:
<% flash.each do |key, value| %>
<%= content_tag :div, value, class: "flash #{key}" %>
<% end %>

Showing Post + Poster's Info on the "Index" Page

I want to show a post along with the poster's info on my rails app. Right now I'm able to show the post and user association on the "show" page (the page for a single post), but when I want to show it on the "index" page (where all of my posts are), I get this error: undefined method `username' for nil:NilClass
I added #post.user = current_user to my post_controller under "show" (That allowed me to show the poster's info. But i dont know what to add to the "def index".
This is what I have there:
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #posts }
end
end
This is what I have under "show"
def show
#post = Post.find(params[:id])
#post.user = current_user
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
I want to know what to add to my def index to be able to display the poster's info. Thanks in advance.
Your set up is wrong. The user needs to be added to the post when it is created and updated.
As you have it now, all posts will always belong to the user that is currently viewing the post because you are assigning the current_user to the post in the show action (and would be doing the same in the index view if you continued with this approach) meaning that when post 1 is viewed by user 1 it would show as belonging to user 1. When post 1 is shown by user 2 it would belong to user 2.
The solution is to assign the current user to the post in the update and create actions of the posts controller exactly as you have it now in the show action but before the post gets physically updated or created.
Then remove the #post.user = current_user from the show action.
This may mean you are left with legacy data in your app that doesn't have a user assigned. But that's o.k., just edit and save each post and it will have you attached automatically.
Then in the views/posts/index_html.erb just add the user details that you want to see in new table row columns before the show/edit links. This is assuming you have a standard scaffolded index.html.erb file. If you don't have a standard index view then put it wherever you want it but then if you had a customised index view you probably wouldn't be asking this question in the first place so forget about that and you'd know where it goes
Update
I did explain how to show the user in the views in my response above but possibly I need to be a little clearer so I'll show you the code.
To show the user name in the show view use
<%= #post.user.name unless #product.user.blank? %>
To show the user in the index action for the post use
<% #posts.each do |post| %> <!-- This is already in your index action. -->
<%=post.user.name unless post.user.blank?%><!-- This is the code you need -->
<!-- You might want to add a new th in the header for the css table then you can add a <td></td> tags round the above line so your table all matches up nicely for display purposes-->
<%end%> <!-- This is already in your index action -->
Hope that helps

Resources