On my site, a user registers and gives their credit card information in the same form. The form uses Devise to save the user. I needed to import the actual devise code into the controller (a bad practice, i know, but with Braintree I couldn't see any other way to wrap the user and charge into one transaction). I finally got the whole thing to work except I am having trouble with Braintree error messages. I just tried creating a transaction with an amount of $2600 which should trigger a Braintree error and display a message, but no such error was triggered. Here is my code:
def create
nonce_from_the_client = params[:payment_method_nonce]
# code taken from devise
build_resource(sign_up_params)
begin
#charge = resource.charges.build
#charge.nonce = nonce_from_the_client
braintree_call = nil
#braintree_errors = nil
ActiveRecord::Base.transaction do
puts "beforer resource saved".green
resource.save!
puts "resource saved".green
puts "charge built".green
puts "before call_braintree".green
braintree_call = #charge.call_braintree!
if braintree_call.success? && !braintree_call.errors.any?
puts "braintree_call was a success".green
#charge.save
#do nothing. All good
else
puts "braintree error".red
#braintree_errors = braintree_call.errors
braintree_call.errors.each do |error|
puts "#{error.code}\n".red
puts "#{error.message}".red
end
raise "#Charge Failed"
end
end
rescue
puts "#charge #{#charge.inspect}".red
puts "#charge errors: #{#charge.errors.inspect}".red
end
yield resource if block_given?
if resource.persisted?
puts "resource persisted".green
if resource.active_for_authentication?
flash[:success] = "Welcome! You have signed up successfully."
# set_flash_message! :notice, :signed_up
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
puts "not sure about this".blue
# set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
flash[:danger] = "signed_up_but_#{resource.inactive_message}"
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
puts "resource did not persist".red
clean_up_passwords resource
set_minimum_password_length
# respond_with resource
puts "NOW WE KNOW".on_red
render 'new' #not sure if this works
end
puts "resource errors are #{resource.errors.inspect}".red
end
And here is the model for Charge that includes the call_braintree method above:
class Charge < ActiveRecord::Base
belongs_to :user
attr_accessor :nonce
after_create :activate_user_account
def activate_user_account
puts "in the activate user account after_Create method".green
self.user.activate
end
def set_amount
amount = self.user.plan.amount * self.user.plan.length
puts "amount is #{amount} and is class #{amount.class}".green
return amount
end
def call_braintree!
puts "in call braintree".green
result = Braintree::Transaction.sale(
:amount => "#{self.set_amount}",
:payment_method_nonce => self.nonce,
:options => {
:submit_for_settlement => true
}
)
end
end
Here are the server logs this generates:
Started POST "/users" for 127.0.0.1 at 2016-02-26 15:06:29 -0500
Processing by Users::RegistrationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Z0jHeFqXJk6iEwdEohilVd2Io85NKoPuzUepjfSXxU0=", "user"=>{"type"=>"Presenter", "plan_id"=>"2", "organization_name"=>"", "first_name"=>"David", "last_name"=>"Applebau,", "email"=>"asfdjhfasdlk#dffadsdf.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "telephone"=>"6462343800", "group_code"=>""}, "field-fnames"=>"", "field-lnames"=>"", "payment_method_nonce"=>"815abd1c-0be5-40e4-8135-a468021c8b75"}
(0.2ms) BEGIN
beforer resource saved
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'asfdjhfasdlk#dffadsdf.com' LIMIT 1
Plan Load (0.1ms) SELECT "plans".* FROM "plans" WHERE "plans"."id" = $1 LIMIT 1 [["id", 2]]
SQL (0.2ms) INSERT INTO "users" ("created_at", "email", "encrypted_password", "first_name", "last_name", "organization_name", "plan_id", "plan_length", "plan_start_date", "plan_status", "telephone", "type", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING "id" [["created_at", "2016-02-26 20:06:29.294712"], ["email", "asfdjhfasdlk#dffadsdf.com"], ["encrypted_password", "$2a$10$jeIa3c.i73f/hBV7ne/Dfu0MAawQzoNak808zLqjjkFeJKmXt78yK"], ["first_name", "David"], ["last_name", "Applebau,"], ["organization_name", ""], ["plan_id", 2], ["plan_length", 1], ["plan_start_date", "2016-02-26 20:06:29.289691"], ["plan_status", "active"], ["telephone", "6462343800"], ["type", "Presenter"], ["updated_at", "2016-02-26 20:06:29.294712"]]
SQL (0.1ms) INSERT INTO "charges" ("created_at", "updated_at", "user_id") VALUES ($1, $2, $3) RETURNING "id" [["created_at", "2016-02-26 20:06:29.307071"], ["updated_at", "2016-02-26 20:06:29.307071"], ["user_id", 20]]
in the activate user account after_Create method
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 20]]
in the after method of the state machine
Plan Load (0.1ms) SELECT "plans".* FROM "plans" WHERE "plans"."id" = $1 LIMIT 1 [["id", 2]]
SQL (0.2ms) UPDATE "users" SET "plan_start_date" = $1, "updated_at" = $2 WHERE "users"."type" IN ('Presenter') AND "users"."id" = 20 [["plan_start_date", "2016-02-26 20:06:29.310158"], ["updated_at", "2016-02-26 20:06:29.311003"]]
resource saved
charge built
before call_braintree
in call braintree
braintree error
(0.2ms) ROLLBACK
ichabod is yolo
#charge #<Charge id: nil, user_id: 20, stripe_id: nil, created_at: "2016-02-26 20:06:29", updated_at: "2016-02-26 20:06:29">
#charge errors: #<ActiveModel::Errors:0x007ffac3f16790 #base=#<Charge id: nil, user_id: 20, stripe_id: nil, created_at: "2016-02-26 20:06:29", updated_at: "2016-02-26 20:06:29">, #messages={}>
resource did not persist
NOW WE KNOW
Rendered layouts/logged_in/_error_messages.html.erb (0.0ms)
#type is Presenter
#free_trial is
in here
Plan Load (0.3ms) SELECT "plans".* FROM "plans" WHERE "plans"."user_type" = 'Presenter' AND ("plans"."name" != 'free_trial') ORDER BY "plans"."display_order_number" ASC
Rendered layouts/logged_in/_registration_form_header.html.erb (1.7ms)
Rendered layouts/logged_in/_payment_form.html.erb (0.1ms)
Rendered layouts/logged_in/_registration_submit_button.html.erb (0.1ms)
Rendered charges/_braintree_js.html.erb (0.0ms)
Rendered devise/registrations/new.html.erb within layouts/logged_in (8.3ms)
Rendered layouts/logged_in/_navbar.html.erb (0.4ms)
resource errors are #<ActiveModel::Errors:0x007ffac3fd6ae0 #base=#<Presenter id: nil, email: "asfdjhfasdlk#dffadsdf.com", encrypted_password: "$2a$10$jeIa3c.i73f/hBV7ne/Dfu0MAawQzoNak808zLqjjkF...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, created_at: "2016-02-26 20:06:29", updated_at: "2016-02-26 20:06:29", confirmation_token: nil, confirmed_at: nil, confirmation_sent_at: nil, organization_id: nil, type: "Presenter", organization_name: "", organization_code: nil, identifier: nil, stripe_customer_id: nil, plan_id: 2, presenter_maximum: nil, workshop_id: nil, presentation_allowance: 2, plan_status: "active", plan_start_date: "2016-02-26 20:06:29", plan_length: 1, contact_name: nil, contact_phone: nil, first_name: "david", last_name: "applebaum", telephone: "6462343800", group_id: nil>, #messages={:plan_id=>[], :organization_name=>[], :first_name=>[], :last_name=>[], :email=>[], :password=>[], :password_confirmation=>[], :telephone=>[], :group_code=>[]}>
Completed 200 OK in 2171ms (Views: 81.0ms | ActiveRecord: 1.8ms)
The only thing I did differently in order to get a braintree error was switch the amount in the call_braintree method with 2600.
As you can see in the logs, some type of error was thrown or the call was not a success, and yet Braintree did not provide an error. Whenever the users card does not work, for whatever reason, I need an error message from braintree to show the user. How do I get braintree errors to properly display?
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact our support team.
Errors are only populated for Braintree validation errors. Processor errors are reported in the Transaction's processor_response_code
Appropriate error handling for Transactions that fail at the processor level would look like this:
if braintree_call.success? && !braintree_call.errors.any?
puts "braintree_call was a success".green
#charge.save
#do nothing. All good
elsif braintree_call.transaction
puts "Transaction processing error: #{braintree_call.transaction.status}: Response Code: #{braintree_call.transaction.processor_response_code}\n".red
raise "#Charge Failed"
else
puts "braintree error".red
#braintree_errors = braintree_call.errors
braintree_call.errors.each do |error|
puts "#{error.code}\n".red
puts "#{error.message}".red
end
raise "#Charge Failed"
end
Check out our docs for more info on transaction statuses and processor response codes
Related
Model:
class Reservation < ApplicationRecord
# https://naturaily.com/blog/ruby-on-rails-enum
enum recurrence: {
daily: 0,
weekly: 1,
monthly: 2,
annually: 3
}, _prefix: :recurring
belongs_to :user
validates :name, :user_id, presence: true
...
end
Migration:
class CreateReservations < ActiveRecord::Migration[5.2]
def change
create_table :reservations do |t|
t.string 'name', null: false
...
t.boolean 'recurring', default: false, null: false
t.integer 'recurrence', index: true, allow_blank: true, default: nil # trying a lot of things here
t.datetime 'expire_time'
...
end
end
end
Works as expected in the console:
2.4.5 :002 > res = Reservation.new(name: 'test', user_id: 1)
=> #<Reservation id: nil, name: "test", recurring: false, recurrence: nil, date: nil, start_time: nil, end_time: nil, expire_time: nil, user_id: 1, created_at: nil, updated_at: nil>
2.4.5 :003 > res.valid?
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
=> true
2.4.5 :004 > res.save
(0.2ms) BEGIN
Reservation Create (0.5ms) INSERT INTO "reservations" ("name", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "test"], ["user_id", 1], ["created_at", "2019-09-24 20:42:40.933959"], ["updated_at", "2019-09-24 20:42:40.933959"]]
(2.1ms) COMMIT
=> true
2.4.5 :005 > res.reload
Reservation Load (0.3ms) SELECT "reservations".* FROM "reservations" WHERE "reservations"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]]
=> #<Reservation id: 5, name: "test", recurring: false, recurrence: nil, date: nil, start_time: nil, end_time: nil, expire_time: nil, user_id: 1, created_at: "2019-09-24 20:42:40", updated_at: "2019-09-24 20:42:40">
2.4.5 :006 > res.recurrence
=> nil
And yet, in Rails Admin, when I create or edit a record, they all get assigned to the first enum. Even when I intentionally delete the value from the form, it still saves the record with the first enum value.
Rails Admin:
config.model Reservation do
weight 2
parent Event
list do
field :name
field :display_date do
formatted_value { bindings[:object].display_date }
end
field :recurrence, :active_record_enum # should be unnecessary, but trying everything
field :expire_time
field :user do
label 'Creator'
formatted_value { bindings[:object].user.name }
end
end
end
I have tried:
not indexing this field (thinking Rails Admin wants a value for indexed columns)
ensuring `:active_record_enum` is declared on `field :recurrence`
As suggested, here's the related section of the log:
Started POST "/admin/reservation/new" for ::1 at 2019-09-25 10:09:50 -0400
Processing by RailsAdmin::MainController#new as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"VbhHM7YD+oQvwMYIQzMHyT5e0MXFFuN3pyH/1s6yM8n6kqKvywZ5E8zaABMKhOU+oWpfU1Kk55FWYjL9TzbkbQ==", "reservation"=>{"name"=>"logs", "date"=>"", "start_time"=>"", "end_time"=>"", "recurring"=>"0", "recurrence"=>"", "expire_time"=>"", "start_time_of_day"=>"", "end_time_of_day"=>"", "day_of_week"=>"", "date_of_month"=>"", "date_of_year"=>"", "user_id"=>"1"}, "return_to"=>"http://localhost:3000/admin/reservation", "_save"=>"", "model_name"=>"reservation"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.2ms) BEGIN
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Reservation Create (0.4ms) INSERT INTO "reservations" ("name", "recurrence", "day_of_week", "date_of_month", "date_of_year", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING "id" [["name", "logs"], ["recurrence", 0], ["day_of_week", ""], ["date_of_month", ""], ["date_of_year", ""], ["user_id", 1], ["created_at", "2019-09-25 14:09:50.926864"], ["updated_at", "2019-09-25 14:09:50.926864"]]
(1.3ms) COMMIT
Redirected to http://localhost:3000/admin/reservation
Completed 302 Found in 26ms (ActiveRecord: 3.0ms)
And, sure enough the param "recurrence"=>"" is transformed into ["recurrence", 0]
It seems to be a known issue, but if it's a legit bug, I still need help with a work-around. Anyone solved this already?
This technically works, but I'm thinking it's not the ideal solution:
If you declare your enum as a hash (not an array) and don't assign anything to 0, it works.
In my case enum recurrence: { daily: 0, weekly: 1, monthly: 2, annually: 3 } becomes enum recurrence: { daily: 1, weekly: 2, monthly: 3, annually: 4 }
The logs show the param isn't passed to the SQL:
Started POST "/admin/reservation/new" for ::1 at 2019-09-25 10:21:17 -0400
Processing by RailsAdmin::MainController#new as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Joyg5VuquwMMKg+ps2zg9t+8r0DZ0iOIkqRlk1cBkemJpkV5Jq84lO8wybL62wIBQIgg1k5gJ25j56i41oVGTQ==", "reservation"=>{"name"=>"test3", "date"=>"", "start_time"=>"", "end_time"=>"", "recurring"=>"0", "recurrence"=>"", "expire_time"=>"", "start_time_of_day"=>"", "end_time_of_day"=>"", "day_of_week"=>"", "date_of_month"=>"", "date_of_year"=>"", "user_id"=>"1"}, "return_to"=>"http://localhost:3000/admin/reservation", "_save"=>"", "model_name"=>"reservation"}
User Load (1.2ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.2ms) BEGIN
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Reservation Create (1.3ms) INSERT INTO "reservations" ("name", "day_of_week", "date_of_month", "date_of_year", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["name", "test3"], ["day_of_week", ""], ["date_of_month", ""], ["date_of_year", ""], ["user_id", 1], ["created_at", "2019-09-25 14:21:17.091292"], ["updated_at", "2019-09-25 14:21:17.091292"]]
(5.7ms) COMMIT
The param contains "recurrence"=>"", but the INSERT INTO doesn't have an array for recurrence like it did before.
And I confirmed in the console:
2.4.5 :001 > res = Reservation.first
Reservation Load (0.4ms) SELECT "reservations".* FROM "reservations" ORDER BY "reservations"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Reservation id: 4, name: "test3", date: nil, start_time: nil, end_time: nil, recurring: false, recurrence: nil, expire_time: nil, start_time_of_day: nil, end_time_of_day: nil, day_of_week: "", date_of_month: "", date_of_year: "", user_id: 1, created_at: "2019-09-25 14:21:17", updated_at: "2019-09-25 14:21:17">
2.4.5 :002 > res.recurrence
=> nil
However, it seems to me like I'm just passing a dis-allowed value and letting Rails reject it. This still seems to be an issue with Rails Admin, specifically with integer enums.
It could be a restriction of using hashes, because nil values should work. Since you're mapping those to 0-indexed values, you should just use a symbol array. The ActiveRecord::Enum documentation (linked above) states:
Note that when an array is used, the implicit mapping from the values to database integers is derived from the order the values appear in the array. In the example, :active is mapped to 0 as it's the first element, and :archived is mapped to 1. In general, the i-th element is mapped to i-1 in the database.
So you should be able to just use an array of symbols for this.
I have a method in my Rails User model that looks like this:
def prep_data
if email.present?
self.email = email.downcase
end
if username.present?
self.username = username.downcase
end
puts "***********************"
puts self.email
puts self.inspect
puts "***********************"
end
When I run this, I'm getting:
***********************
john#me.com
#<User id: nil, username: nil, email: nil, password_salt: nil, password_digest: nil, created_at: nil, updated_at: nil>
***********************
I'm at a loss to explain why self.email seems to be set, but then when I inspect self, it is nil. This is also causing the object to not save because it's nil. A more complete version of the log is
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓","authenticity_token"=>"7ocuB7GiyPjMZ84SHKo9CDSPjY8uOdtDc5A9wr+stzTPrIHnvfxAkdp1HxWActd07ZWzJVEBH43A3V/4sX1ixg==", "user"=>{"username"=>"John", "email"=>"John#me.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create User"}
(0.0ms) begin transaction
***********************
john#me.com
#<User id: nil, username: nil, email: nil, password_salt: nil, password_digest: nil, created_at: nil, updated_at: nil>
***********************
***********************
john#me.com
#<User id: nil, username: nil, email: nil, password_salt: "$2a$10$T92qOVBwjGm4t550POLVHu", password_digest: "$2a$10$T92qOVBwjGm4t550POLVHuXhss6lniJJekxMbeKR/yU...", created_at: nil, updated_at: nil>
***********************
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."username" = 'john' LIMIT 1
User Exists (0.0ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'john#me.com' LIMIT 1
SQL (0.7ms) INSERT INTO "users" ("password_salt", "password_digest", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["password_salt", "$2a$10$T92qOVBwjGm4t550POLVHu"], ["password_digest", "$2a$10$T92qOVBwjGm4t550POLVHuXhss6lniJJekxMbeKR/yU.79uMqtZJa"], ["created_at", "2016-09-23 17:51:31.692923"], ["updated_at", "2016-09-23 17:51:31.692923"]]
SQLite3::ConstraintException: NOT NULL constraint failed: users.username: INSERT INTO "users" ("password_salt", "password_digest", "created_at", "updated_at") VALUES (?, ?, ?, ?)
The end goal is to get this to actually save. You can see that when rails does the Insert, username and email are nil, but I can tell from the debug puts statements that the email and username exist and are getting at least to the validation. That said, even it I remove the validation completely, I have this problem.
Have you tried this?
self.email.downcase! if email.present?
#arieljuod solved this for me by recommending that I use custom setters instead of trying to change things before validation. Problem solved although I don't fully understand why the other way didn't work even if it wasn't "best"
def username=(value)
self[:username] = value.downcase
end
def email=(value)
self[:email] = value.downcase
end
You need to call save on the model. So just call self.save on the model.
I have 2 models with a one-to-many association: User and Recipe. the User class has_many :recipes while the Recipe class belongs_to :user. I've already run the migration, reloaded the rails console, and checked to make sure that user_id is a column in the recipes table. Still, I get an undefined method error when I try to append a recipe to a user:
2.0.0-p598 :047 > user.recipes << Recipe.first
NoMethodError: undefined method `recipes' for #<User:0x00000004326fa0>
here is the migration code (I've already run rake db:migrate):
class AddUserIdToRecipes < ActiveRecord::Migration
def change
add_column :recipes, :user_id, :integer
end
end
Here is the User model code:
class User < ActiveRecord::Base
has_one :profile
has_many :recipes
end
Here is the Recipe model code:
class Recipe < ActiveRecord::Base
validates_presence_of :title, :body
belongs_to :user
def long_title
"#{title} - #{published_at}"
end
end
Why does recipes still show up as an undefined method?
Try this on your console:
irb(main):007:0> user = User.new first_name: 'John', last_name: 'Doe'
=> #<User id: nil, first_name: "John", last_name: "Doe", created_at: nil, updated_at: nil>
irb(main):008:0> user.save
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "users" ("created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?) [["created_at", "2015-01-19 21:14:33.489371"], ["first_name", "John"], ["last_name", "Doe"], ["updated_at", "2015-01-19 21:14:33.489371"]]
(0.6ms) commit transaction
=> true
irb(main):009:0> r = Recipe.new name: 'oooohh awesome', description: 'my description goes here'
=> #<Recipe id: nil, name: "oooohh awesome", description: "my description goes here", created_at: nil, updated_at: nil, user_id: nil>
irb(main):010:0> r.save
(0.1ms) begin transaction
SQL (0.2ms) INSERT INTO "recipes" ("created_at", "description", "name", "updated_at") VALUES (?, ?, ?, ?) [["created_at", "2015-01-19 21:15:16.548090"], ["description", "my description goes here"], ["name", "oooohh awesome"], ["updated_at", "2015-01-19 21:15:16.548090"]]
(1.2ms) commit transaction
=> true
irb(main):011:0> user.recipes << Recipe.first
Recipe Load (0.2ms) SELECT "recipes".* FROM "recipes" ORDER BY "recipes"."id" ASC LIMIT 1
(0.0ms) begin transaction
SQL (0.2ms) UPDATE "recipes" SET "updated_at" = ?, "user_id" = ? WHERE "recipes"."id" = 1 [["updated_at", "2015-01-19 21:15:49.181586"], ["user_id", 1]]
(1.3ms) commit transaction
Recipe Load (0.2ms) SELECT "recipes".* FROM "recipes" WHERE "recipes"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Recipe id: 1, name: "oooohh awesome", description: "sper long deskdk", created_at: "2015-01-19 21:10:24", updated_at: "2015-01-19 21:15:49", user_id: 1>]>
irb(main):012:0> user.save
(0.1ms) begin transaction
(0.0ms) commit transaction
=> true
irb(main):013:0> user.recipes
=> #<ActiveRecord::Associations::CollectionProxy [#<Recipe id: 1, name: "oooohh awesome", description: "sper long deskdk", created_at: "2015-01-19 21:10:24", updated_at: "2015-01-19 21:15:49", user_id: 1>]>
irb(main):014:0> user.recipes.first
=> #<Recipe id: 1, name: "oooohh awesome", description: "sper long deskdk", created_at: "2015-01-19 21:10:24", updated_at: "2015-01-19 21:15:49", user_id: 1>
irb(main):015:0>
you can see that Recipe.first has been inserted into user.recipes and its saved.
I made two models similar to yours, and have exactly the same setup as you. You can follow code above to write your controllers.
In my Post.rb model, I am doing this:
validates_presence_of :body
In my controller I have this action that I am executing:
def mark_as_published
if #post.unpublished?
#post.published!
redirect_to post_path(#post), notice: "Successfully published."
else
redirect_to post_path(#post), notice: "Post already published"
end
end
This is the error I am getting:
app/controllers/posts_controller.rb:104:in `mark_as_published'
Started PUT "/posts/ebola-death-climbs-past-6-000/mark_as_published" for 67.230.41.168 at 2014-12-10 20:15:29 +0000
app[web.1]: ActiveRecord::RecordInvalid (Validation failed: Body can't be blank):
This is the record in the console:
> Post.last
=> #<Post id: 29, title: "Ebola death climbs past 6,000", photo: nil, body: "Fresh figures from WHO has revealed that the death...", created_at: "2014-12-10 20:08:58", updated_at: "2014-12-10 20:08:58", user_id: 10, ancestry: nil, file: nil, status: 2, slug: "ebola-death-climbs-past-6-000", publication_status: 0, has_eyewitness: false, youtube_embed_code: "", soundcloud_embed_code: "">
Why does this validation fail, only on the save/update, even though the post.body is not blank?
Edit 1
The publication_status is just an enum:
enum publication_status: [ :unpublished, :published ]
Which comes with a set of handy methods, including published?, unpublished?, published!, and unpublished!. The latter two basically toggle the value to be the flag, i.e. unpublished! changes the publication_status to be unpublished and vice versa.
Here are other validations on the Post.rb model:
validates_length_of :body, maximum: 150, too_long: 'The report must be less than 150 words.',
tokenizer: ->(str) { str.scan(/\w+/) }
validates_length_of :title, maximum: 7, too_long: 'The title must be less than 7 words.',
tokenizer: ->(str) { str.scan(/\w+/) }
Edit 2
This is what happens when I mark the record as published in the console:
0> p = Post.last
=> #<Post id: 29, title: "Ebola death climbs past 6,000", photo: nil, body: "Fresh figures from WHO has revealed that the death...", created_at: "2014-12-10 20:08:58", updated_at: "2014-12-10 20:08:58", user_id: 10, ancestry: nil, file: nil, status: 2, slug: "ebola-death-climbs-past-6-000", publication_status: 0, has_eyewitness: false, youtube_embed_code: "", soundcloud_embed_code: "">
irb(main):002:0> p.published!
=> true
irb(main):003:0> p.save
=> true
No SQL is generated. Not sure if this is because I am doing this in production on Heroku.
Edit 3
I tried to publish a record in development, and this is the SQL & server log:
Started PUT "/posts/longword-verylongword-longword-longword-longword-vellylongword-prettylongword/mark_as_published" for 127.0.0.1 at 2014-12-10 19:22:56 -0500
Processing by PostsController#mark_as_published as HTML
Parameters: {"authenticity_token"=>"8kYDxjYS54sGozjSS4ZZwQFJUTtIBgLpEmpAlTRZc4k=", "id"=>"longword-verylongword-longword-longword-longword-vellylongword-prettylongword"}
User Load (1.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1
(2.0ms) SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 1]]
(1.2ms) SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'editor') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 1]]
(0.2ms) BEGIN
(0.2ms) ROLLBACK
Completed 422 Unprocessable Entity in 62ms
ActiveRecord::RecordInvalid - Validation failed: Body can't be blank:
When I do it in the console, this is the log:
> p = Post.last
Post Load (0.6ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT 1
=> #<Post id: 37, title: "LongWord VeryLongWord LongWord LongWord LongWord V...", photo: nil, body: "10PP gives you a lot of one on one attention that ...", created_at: "2014-11-27 09:21:06", updated_at: "2014-11-27 09:21:06", user_id: nil, ancestry: nil, file: nil, status: 1, slug: "longword-verylongword-longword-longword-longword-v...", publication_status: 0, has_eyewitness: false, youtube_embed_code: "", soundcloud_embed_code: "">
[21] pry(main)> p.published!
(0.2ms) BEGIN
SQL (2.0ms) UPDATE "posts" SET "publication_status" = $1, "updated_at" = $2 WHERE "posts"."id" = 37 [["publication_status", 1], ["updated_at", "2014-12-11 00:24:18.419390"]]
FriendlyId::Slug Load (1.9ms) SELECT "friendly_id_slugs".* FROM "friendly_id_slugs" WHERE "friendly_id_slugs"."sluggable_id" = $1 AND "friendly_id_slugs"."sluggable_type" = $2 ORDER BY "friendly_id_slugs".id DESC LIMIT 1 [["sluggable_id", 37], ["sluggable_type", "Post"]]
(0.9ms) COMMIT
=> true
From the log I can see clearly you do not retrieve the Post from the database. So you are saving a "new/empty" post. Please verify how your #post variable is set. I am guessing you are using the incorrect before_action now, or your post retrieval is too liberal and defaults to a new post if not found?
One option, if and only if it works within your application's logic, is to limit the validation to creates:
validates_presence_of :body, on: :create
If your app's logic dictates that updates need to run the validation, it would be helpful to see more code in order to better know how #post is populated, what the published/unpublished? methods look like, etc.
I am using Devise for my user authentication and would like to destroy an associated profile along with the user.
My failing spec looks like this:
it "should destroy associated profile" do
profile = #user.profile
#user.destroy
expect(profile).to be_nil
end
And
In my user model:
has_one :profile, dependent: :destroy
In my profile model:
belongs_to :user
In the console, I can reproduce the issue like this:
2.0.0p247 :001 > #user = FactoryGirl.create(:user)
(1.5ms) BEGIN
User Exists (2.9ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'person946979#example.com' LIMIT 1
User Exists (1.7ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('person946979#example.com') LIMIT 1
SQL (15.7ms) INSERT INTO "users" ("created_at", "email", "encrypted_password", "name", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Fri, 16 Aug 2013 01:21:12 UTC +00:00], ["email", "person946979#example.com"], ["encrypted_password", "$2a$10$0704XOlw.6ZE4HEfDhaIeuwnEbbJZvZda3Jwr052aLS5z3G77Dgja"], ["name", "Example User"], ["updated_at", Fri, 16 Aug 2013 01:21:12 UTC +00:00]]
SQL (3.8ms) INSERT INTO "profiles" ("created_at", "updated_at", "user_id") VALUES ($1, $2, $3) RETURNING "id" [["created_at", Fri, 16 Aug 2013 01:21:12 UTC +00:00], ["updated_at", Fri, 16 Aug 2013 01:21:12 UTC +00:00], ["user_id", 25]]
Profile Load (3.4ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."user_id" = $1 ORDER BY "profiles"."id" ASC LIMIT 1 [["user_id", 25]]
(2.2ms) COMMIT
=> #<User id: 25, email: "person946979#example.com", encrypted_password: "$2a$10$0704XOlw.6ZE4HEfDhaIeuwnEbbJZvZda3Jwr052aLS5...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, created_at: "2013-08-16 01:21:12", updated_at: "2013-08-16 01:21:12", name: "Example User">
2.0.0p247 :002 > #user.destroy
(1.0ms) BEGIN
SQL (2.5ms) DELETE FROM "profiles" WHERE "profiles"."id" = $1 [["id", 4]]
SQL (5.4ms) DELETE FROM "users" WHERE "users"."id" = $1 [["id", 25]]
(2.0ms) COMMIT
=> #<User id: 25, email: "person946979#example.com", encrypted_password: "$2a$10$0704XOlw.6ZE4HEfDhaIeuwnEbbJZvZda3Jwr052aLS5...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, created_at: "2013-08-16 01:21:12", updated_at: "2013-08-16 01:21:12", name: "Example User">
Interestingly, the user appears to actually have been deleted.
2.0.0p247 :003 > #user.reload.destroy
User Load (2.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 25]]
ActiveRecord::RecordNotFound: Couldn't find User with id=25
What is going on here?
Your model seems fine. Try something like this:
it "should destroy associated profile" do
profile = #user.profile
#user.destroy
expect(Profile.find(profile.id)).to be_nil
end
Like Heungju said, while the database row that corresponds to profile is being destroyed, the variable itself isn't.
How about this?
it "should destroy associated profile" do
profile = #user.profile
#user.destroy
expect(#user.profile).to be_nil
end
After #user.destroy, the 'profile', variable that expected to be nil, was not changed. I think...
Rewriting my spec like this does what I need:
it "should destroy associated profile" do
expect {
#user.destroy
}.to change(Profile, :count).by(-1)
end