App rolling back transactions and I don't know why - sql

I am curious as to why my Rails app is rolling back transactions to DB without it being asked to.
Puma log:
Started POST "/posts" for 127.0.0.1 at 2019-01-05 00:32:32 -0500
Processing by PostsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"1AlwhyE0VY87oSyCyjmrzgxinJ7+t1TfoYYEDXvfl0pGd4DKE842KXHroFWgtXeusOgt+ZApHmB+e40qliTPjQ==", "post"=>{"title"=>"test", "category_id"=>"4", "body"=>"test"}, "commit"=>"Create Post"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 12], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:7
(0.2ms) begin transaction
↳ app/controllers/posts_controller.rb:14
Category Load (0.2ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]]
↳ app/controllers/posts_controller.rb:14
(0.1ms) rollback transaction
↳ app/controllers/posts_controller.rb:14
Redirected to http://localhost:3000/posts
Completed 302 Found in 18ms (ActiveRecord: 0.8ms)
Post controller:
class PostsController < ApplicationController
before_action :require_user
def index
#posts = Post.all
end
def new
#post = Post.new
#categories = Category.all
end
def create
#post = Post.new(post_params)
if #post.save(post_params)
#post.author = current_user.username
flash[:success] = "Post created."
redirect_to post_path(#post.id)
else
flash[:danger] = "Post not created. Redirecting to main page....."
redirect_to posts_path
end
end
View:
<%= form_for #post do |f| %>
<%= f.label :title, "Title of Post" %>
<%= f.text_field :title, class: "form-control" %>
<%= f.label :body, "Post Text" %>
<%= f.text_area :body, class: "form-control" %>
<%= f.submit "Create Post", class: "btn btn-info"%>
<% end %>

You're likely to have validation of the presence of the author field. Since it's set incorrectly in the create method, you have the transaction rolled back:
class PostsController < ApplicationController
...
def create
#post = Post.new(post_params)
if #post.save(post_params)
#post.author = current_user.username
flash[:success] = "Post created."
redirect_to post_path(#post.id)
...
end
In order to have author assigned and saved in the db, you need to assign the author before the saving:
class PostsController < ApplicationController
...
def create
#post = Post.new(post_params)
#post.author = current_user.username
if #post.save(post_params)
flash[:success] = "Post created."
redirect_to post_path(#post.id)
...
end

Depending on the version of Rails you have, and if you are using Strong Parameters, you must whitelist your parameters in the controller.
Example from docs:
private
# Using a private method to encapsulate the permissible parameters
# is just a good pattern since you'll be able to reuse the same
# permit list between create and update. Also, you can specialize
# this method with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end

Related

Admin's delete user link not working with Devise and Rails 7

I want to allow admins to delete users from a link on the users index.
Desired behavior: When logged in admin clicks "delete" next to a username on the index page, that admin sees a confirmation message. Upon confirming, the user is deleted, admin sees updated index with flash success message.
I've tried two different ways, both failing. All things being equal, I'd prefer to do this using Devise out of the box functionality (e.g. the user_registration_path destroy action, and default flash message).
First way - adding a destroy action to custom user controller:
users/index.html.erb
<% provide(:title, 'All users') %>
<h2>All users</h2>
<%= will_paginate %>
<ul class="users">
<%= render #users %>
</ul>
<%= will_paginate %>
users/_user.html.erb
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
<% if current_user.admin? && current_user != user %>
| <%= link_to "delete", user, data: { "turbo-method": :delete,
turbo_confirm: "You sure?" } %>
<% end %>
</li>
users_controller.rb
class UsersController < ApplicationController
before_action :authenticate_user!, :only => [:index, :edit, :update, :destroy]
def show
#user = User.find(params[:id])
end
def index
#users = User.paginate(page: params[:page])
end
def destroy
#user = User.find(params[:id])
#user.destroy
flash[:success] = "User deleted"
redirect_to users_url, status: :see_other
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations'
}
root "static_pages#home"
get "/about", to: "static_pages#about"
get "/contact", to: "static_pages#contact"
resources :users
end
Using this code, when I click "delete" next to the username, the user show page is loaded with these params:
#<ActionController::Parameters {"controller"=>"users", "action"=>"show", "id"=>"3"} permitted: false>
Second way - using the destroy action in the registrations_controller:
users/_user.html.erb
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
<% if current_user.admin? && current_user != user %>
| <%= link_to "delete", user_registration_path, data: { "turbo-method": :delete,
turbo_confirm: "You sure?" } %>
<% end %>
</li>
registrations_controller.rb
# frozen_string_literal: true
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
before_action :authenticate_user!, :only => [:destroy]
# GET /resource/sign_up
# def new
# super
# end
# POST /resource
# def create
# super
# end
# GET /resource/edit
# def edit
# super
# end
# PUT /resource
# def update
# super
# end
# DELETE /resource
def destroy
super
end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
# def cancel
# super
# end
# protected
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
end
# If you have extra params to permit, append them to the sanitizer.
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [:name])
end
# The path used after sign up.
def after_sign_up_path_for(resource)
super(resource)
user_url(#user)
end
# The path used after sign up for inactive accounts.
# def after_inactive_sign_up_path_for(resource)
# super(resource)
# end
end
Using this code, when I click "delete" next to the username, the users index page is reloaded with params:
#<ActionController::Parameters {"controller"=>"users", "action"=>"index"} permitted: false>
This worked for me:
<%= button_to "Delete user", user, form: {data: { turbo_confirm: "Are you sure?" }}, method: :delete %>

Insert Query is Wrong on Rails

So i am trying to learn Ruby on Rails and i tried to code simple product creation to database but rails only executes the insert query with only created_at and updated_at values. Here is my products_controller's create action ;
def create
#product = Product.new(params[:product_params])
#product.save
redirect_to #product
end
private
def product_params
params.require(:product).permit(:title, :price)
end
Here is my new.html.erb under products folder ;
<%= form_with scope: :product ,url:products_path, local: true do |form| %>
<p>
<%= form.text_field :title %>
</p>
<p>
<%= form.text_field :price %>
</p>
<p>
<%= form.submit %>
<% end %>
And this is the SQL query executed by Rails looking to Rails Server
Started POST "/products" for 127.0.0.1 at 2018-12-04 16:44:52 +0300
Processing by ProductsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"9Z4aFNpMv+uUPXx+LkHoOtYpWae/8tIOCQ3Jt47AFScp/vVHTWI+G4d6CjHqBz6t5L5UR57I7Gh7bUWog1Dqow==", "product"=>{"title"=>"brokoli", "price"=>"3434"}, "commit"=>"Save Product"}
(0.2ms) BEGIN
↳ app/controllers/products_controller.rb:8
Product Create (0.3ms) INSERT INTO products (created_at, updated_at) VALUES ('2018-12-04 13:44:52', '2018-12-04 13:44:52')
↳ app/controllers/products_controller.rb:8
(5.4ms) COMMIT
↳ app/controllers/products_controller.rb:8
Redirected to http://localhost:3000/products/12
Completed 302 Found in 25ms (ActiveRecord: 7.4ms)
You don't do
#product = Product.new(params[:product_params])
You do
#product = Product.new(product_params)
The product_params is a method that returns the white-listed params entries.
def create
#product = Product.new(product_params) #product.save
redirect_to #product
end
private
def product_params params.require(:product).permit(:title, :price)
end

Invalid Form Entry Causes Authlogic to Rollback to a Different Form

(Using Rails 3) I have authlogic set up to manage my users and user sessions (with a user controller and user_sessions controller and associated views, models, etc). I have two levels of user: an admin and a client. The admin can create users and edit users, and users are only able to look at documents on the website (no user editing or viewing privileges at all).
Then I implemented another controller (reset_password) which allows clients to change their password on the website. To do this I allowed clients to edit clients (so they can edit themselves). But The form that allows clients to change their password is much more limited than the form that allows admins to make clients.
The problem I am having is that when a client goes to the password editing form, and enters an invalid password or password confirmation, the page refreshes to the full admin form that lets clients change their privileges, etc. What I need is a way to force a failed form entry to return the client to the same form that they were just on rather than the admin form.
The problem, I am pretty sure, is because both the admin form and the client form use 'form_for #user do |f|' to define the form. I believe that rails cannot tell the two apart.
Here are the relevant files:
My admin form (users#edit with rendered form):
= stylesheet_link_tag :universal
= form_for #user do |f|
- if #user.errors.any?
#error_explanation
%span.boldHeader= "#{pluralize(#user.errors.count, "error")} prohibited this user from being saved:"
%ul
- #user.errors.full_messages.each do |msg|
%li= msg
.fieldContainer
.field
= f.label :username
= f.text_field :username
.field
= f.label :role
= f.collection_select :role, User::ROLES, :to_s, :humanize, { :selected => :client }
.fieldContainer
.field
= f.label :password
= f.password_field :password
.field
= f.label :password_confirmation
= f.password_field :password_confirmation
.fieldContainer
.field
= f.label :first_name
= f.text_field :first_name
.field
= f.label :last_name
= f.text_field :last_name
.field
= f.label :firm_name
= f.text_field :firm_name
.fieldContainer
.field
= f.label :email
= f.text_field :email
.field
= f.label :phone
= f.text_field :phone
.actions
= f.submit 'Save'
%a.button{ :href => "/users" }Cancel
My client form (reset_password#edit with rendered form):
= stylesheet_link_tag :universal
= form_for #user do |f|
- if #user.errors.any?
#error_explanation
%span.boldHeader= "#{pluralize(#user.errors.count, "error")} prohibited this user from being saved:"
%ul
- #user.errors.full_messages.each do |msg|
%li= msg
.fieldContainer
.field
= f.label :password
= f.password_field :password
.field
= f.label :password_confirmation
= f.password_field :password_confirmation
.actions
= f.submit 'Save'
%a.button{ :href => "/users" }Cancel
Client form update method:
def update
#user = current_user
if #user.update_attributes(params[:user])
format.html { redirect_to #user, notice: 'Password successfully changed.' }
format.json { head :no_content }
else
format.json { render json: #user.errors, status: :unprocessable_entity }
format.html { redirect_to "/settings" }
end
end
Admin form update method:
def update
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
Note: /settings routes to reset_passwords#edit which has a partial render in it which is how I display the client form (see above). I also have users, user_sessions, and reset_passwords as resources in my routes file.
Finally, here is what I am seeing in the production log. You can see that I start in reset_password/_form (the client form) but then I get rolled back to users/_form (the admin form).
Started GET "/settings" for 127.0.0.1 at 2013-07-02 16:26:52 -0400
Processing by ResetPasswordsController#edit as HTML
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Rendered reset_passwords/_form.html.haml (4.0ms)
Rendered reset_passwords/edit.html.haml within layouts/application (14.0ms)
Rendered shared/_header.html.haml (1.0ms)
Completed 200 OK in 57ms (Views: 54.0ms | ActiveRecord: 0.0ms)
[2013-07-02 16:26:52] WARN Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true
Started GET "/assets/global.css?body=1" for 127.0.0.1 at 2013-07-02 16:26:52 -0400
Served asset /global.css - 304 Not Modified (0ms)
[2013-07-02 16:26:53] WARN Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true
Started PUT "/users/1" for 127.0.0.1 at 2013-07-02 16:26:58 -0400
Processing by UsersController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"9KeHrOySL3FRhSnfYtVvKl2rCz9EPBSyGmtGGasDxnA=", "user"=>{"password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Save", "id"=>"1"}
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", "1"]]
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
(0.0ms) begin transaction
User Exists (0.0ms) SELECT 1 AS one FROM "users" WHERE ("users"."persistence_token" = '5d1007bf94b34a7de44707e4ca432b18338f1c9ef6c5f99bf04845096a6b880057b5b43c917b9d38f790521ef62147eec3d9302471da73967048b48b4a399b18' AND "users"."id" != 1) LIMIT 1
(1.0ms) rollback transaction
Rendered users/_form.html.haml (9.0ms)
Rendered users/edit.html.haml within layouts/application (19.0ms)
Rendered shared/_header.html.haml (0.0ms)
Completed 200 OK in 80ms (Views: 61.0ms | ActiveRecord: 1.0ms)
I needed to specify the route for the form to take. Previously I had this in my "client" form (reset_password#edit with rendered form):
= form_for #user do |f|
I changed this to the following:
= form_for #user, :url => reset_password_path(#user) do |f|
I then also made some changes to the controller for the form:
def update
#user = current_user
#user.password = params[:user][:password]
#user.password_confirmation = params[:user][:password_confirmation]
respond_to do |format|
if !#user.changed?
format.html { redirect_to "/settings", notice: 'Form incomplete' }
format.json { render json: #user.errors, status: :unprocessable_entity }
elsif #user.save
format.html { redirect_to "/index", notice: 'Password successfully changed' }
format.json { head :no_content }
else
format.html { redirect_to "/settings", notice: 'Form incomplete' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
The part that I needed was the respond_to do |format|, the rest are just some logic changes to make it work better for my application.

Rails: How to include form for related model

I have a Rails app using a "has_many :through" relationship. I have 3 tables/models. They are Employee, Store, and StoreEmployee. An employee can work for one or more stores. On the Store view (show.html.erb), I want to include a form to add an employee to the store. If the employee doesn't already exist, it is added to the Employees table. If it does already exist, it is just added to the store.
Here are the model definitions:
# models
class Employee < ActiveRecord::Base
has_many :store_employees
has_many :stores, :through => :store_employees
attr_accessible :email, :name
end
class Store < ActiveRecord::Base
has_many :store_employees
has_many :employees, :through => :store_employees
attr_accessible :address, :name
end
class StoreEmployee < ActiveRecord::Base
belongs_to :store
belongs_to :employee
attr_accessible :employee_id, :store_id
end
Using the rails console, I can prove the data models are working correctly, like so:
emp = Employee.first
str = Store.first
str.employees << emp # add the employee to the store
str.employees # shows the employees that work at the store
emp.stores # shows the stores where the employee works
I have also updated my routes.rb to nest the employees resource under the stores resource like this:
resources :stores do
resources :employees
end
Edit: I also added the resources :employees as "unnested" resource. So that now looks like this:
resources :stores do
resources :employees
end
resources :employees
So, in the store controller's show action, I think it should look like this:
def show
#store = Store.find(params[:id])
#employee = #store.employees.build # creating an empty Employee for the form
respond_to do |format|
format.html # show.html.erb
format.json { render json: #store }
end
end
And here is the store's show.html.erb including the form to add an employee:
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= #store.name %>
</p>
<p>
<b>Address:</b>
<%= #store.address %>
</p>
<%= form_for([#store, #employee]) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.submit "Add Employee" %>
<% end %>
<%= link_to 'Edit', edit_store_path(#store) %> |
<%= link_to 'Back', stores_path %>
I get this error message when I click the 'Add employee' button:
NoMethodError in EmployeesController#create
undefined method `employee_url' for #
So, what I am I missing? I think it has to do with the 'form_for' but I'm not sure. I am not sure why the EmployeesController#create is getting called. Am I doing something wrong with the routes.rb?
I don't know where the logic for adding the employee should go. In the Employees controller? Store Controller?
### Update
Here is the updated create method in the EmployeesController. I included the recommendation from Mischa's answer.
def create
#store = Store.find(params[:store_id])
#employee = #store.employees.build(params[:employee])
respond_to do |format|
if #employee.save
format.html { redirect_to #store, notice: 'Employee was successfully created.' }
format.json { render json: #employee, status: :created, location: #employee }
else
format.html { render action: "new" }
format.json { render json: #employee.errors, status: :unprocessable_entity }
end
end
end
After making this change, I successfully saved the Employee but the store_employees association record is not created. I have no idea why. Here is the output in my terminal when creating a record. Note the store is SELECTed and the Employee is INSERTed but that is all. No store_employees record anywhere:
Started POST "/stores/1/employees" for 127.0.0.1 at 2012-10-10
13:06:18 -0400 Processing by EmployeesController#create as HTML
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"LZrAXZ+3Qc08hqQT8w0MhLYsNNSG29AkgCCMkEJkOf4=",
"employee"=>{"name"=>"betty", "email"=>"betty#kitchen.com"},
"commit"=>"Add Employee", "store_id"=>"1"} Store Load (0.4ms)
SELECT "stores".* FROM "stores" WHERE "stores"."id" = ? LIMIT 1
[["id", "1"]] (0.1ms) begin transaction SQL (13.9ms) INSERT
INTO "employees" ("created_at", "email", "name", "updated_at") VALUES
(?, ?, ?, ?) [["created_at", Wed, 10 Oct 2012 17:06:18 UTC +00:00],
["email", "betty#kitchen.com"], ["name", "betty"], ["updated_at", Wed,
10 Oct 2012 17:06:18 UTC +00:00]] (1.3ms) commit transaction
Redirected to http://localhost:3000/stores/1 Completed 302 Found in
23ms (ActiveRecord: 15.6ms)
The problem is caused by this snippet from your EmployeesController#create method:
redirect_to #employee
This tries to use employee_url, but that does not exist, because you have nested the employees resource in your routes file. Solution is to also add the "unnested" resource:
resources :stores do
resources :employees
end
resources :employees
Or simply redirect to a different location. E.g. back to StoresController#show.
By the way, the default scaffolding won't do what you expect it to do: it won't create an employee that is related to a store. For that you have to rewrite it somewhat:
def create
#store = Store.find(params[:store_id])
#employee = #store.employees.build(params[:employee])
respond_to do |format|
if #employee.save
format.html { redirect_to #employee, notice: 'Employee was successfully created.' }
format.json { render json: #employee, status: :created, location: #employee }
else
format.html { render action: "new" }
format.json { render json: #employee.errors, status: :unprocessable_entity }
end
end
end

The terminal says that the view was rendered but it doesn't actually renders in the browser?

The following controller does the following:
If the current user's ID exists in the user_id attribute of one of the vote instances, the current user will be redirected to this page with a notice telling him that he or he already voted. If not, the vote will be cast and render views/votes/vote_up.js.erb.
votes_controller.erb:
class VotesController < ApplicationController
def vote_up
#post = Post.find(params[:id])
if #post.votes.exists?(:user_id => current_user.id)
redirect_to #post, notice: 'You already voted'
else
#vote = #post.votes.create(:user_id => current_user.id, :polarity => 1)
respond_to do |format|
format.js
end
end
end
end
views/posts/show.html.erb:
<div class="post-<%=#post.id%>">
<h3><span class="vote-count"><%= #post.votes.count %></span>votes</h3><br />
<%= link_to "Vote Up", vote_up_path(#post), :remote => true, :class => "vote-up" %><br />
</div>
Everything works Ok except that the user is not being redirected and the notice is not appearing. I just get this notice in the terminal:
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2
ORDER BY users.created_at ASC LIMIT 1 Rendered posts/show.html.erb
within layouts/application (386.5ms) Completed 200 OK in 428ms (Views:
416.5ms | ActiveRecord: 7.3ms)
Any suggestions to fix this? (by the way, is it more redable to use unless here (how)?
Redirect doesn't work because you are calling the vote_up method via Ajax. You should display the notice with javascript, just like you are doing when the user hasn't voted yet.
You could do something like this:
def vote_up
#post = Post.find(params[:id])
if #post.votes.exists?(:user_id => current_user.id)
#notice = 'You already voted'
else
#vote = #post.votes.create(:user_id => current_user.id, :polarity => 1)
end
respond_to do |format|
format.js
end
end
Then in your .erb.js file, you send back javascript depending on whether #notice and #vote are blank or not.
<% unless #notice.blank? %>
// javascript to display #notice
<% end %>
<% unless #vote.blank? %>
// javascript to increase vote count
<% end %>