Why is this callback executed? - ruby-on-rails

I want a validation to run before a record gets updated. I know of before_update but I pretty much copy and pasted the first codesnippet out of the api docs.
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
My stripped down model looked then like
class User < ActiveRecord::Base
attr_accessible :email
validates :email, :presence => true
before_save(:on => :update) do
puts "******** before_save on => :update ********"
# do something
end
end
if I go into the console and do create a new entry this callback is being executed on a SQL insert call.
irb(main):001:0> User.new(:email => "test#test.com").save
(0.1ms) begin transaction
******** before_save on => :update ********
SQL (29.1ms) INSERT INTO "users" ("created_at", "email", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["created_at", Fri, 30 Mar 2012 00:26:33 UTC +00:00], ["email", "test#test.com"], ["first_name", nil], ["last_name", nil], ["updated_at", Fri, 30 Mar 2012 00:26:33 UTC +00:00]]
(433.1ms) commit transaction
=> true
irb(main):002:0>
I would have expected to see this only on an update call. Can anybody sheed some light on this?
[EDIT]
I just changed the callback into a function call with no change in the outcome. The callback is still executed on create.
class User < ActiveRecord::Base
attr_accessible :email
validates :email, :presence => true
before_save :my_before_update, :on => :update
private
def my_before_update
puts "******** before_save on => :update ********"
# do something
end
end
The output is the same.
Loading development environment (Rails 3.2.2)
irb(main):001:0> User.new(:email => "test#test.com").save
(0.1ms) begin transaction
******** before_save on => :update ********
SQL (28.2ms) INSERT INTO "users" ("created_at", "email", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["created_at", Fri, 30 Mar 2012 02:28:45 UTC +00:00], ["email", "test#test.com"], ["first_name", nil], ["last_name", nil], ["updated_at", Fri, 30 Mar 2012 02:28:45 UTC +00:00]]
(131.2ms) commit transaction
=> true

The ActiveRecord::Callbacks don't support an :on option...
From the Rails codebase, the only place that mentions handling an :on option is in the validations module code in ActiveModel::Validations.
If you look through the ActiveRecord::Callbacks code, you'll see that there's no mention of :on, nor does the ActiveRecord::Callbacks module include any of the ActiveModel::Validations module that will handle that option. There is an include for ActiveModel::Validations::Callbacks, but that will just provide the definitions for the before_ and after_ validations methods. However, the before_validation and after_validation callbacks will handle the :on option as seen here in their definitions.

I'm pretty sure this is one of those areas that the Rails API has changed across versions. I do recall there being a way to pass :on as an option to before_save, just as I recall when you had to define an after_initialize method (it wasn't available as a callback).
The current way is cleaner and more explicit.
If you do find that the current docs reference before_save(:on => :update), check out the new docrails Github repository where you can fork, change, and commit changes to the docs to be included (no pull requests necessary, or accepted).

After a little more research, looks like you're right, it looks like you can pass :on => :update to before_save
Maybe the issue comes from the block notation, try calling a function like this:
before_save :run_this_before_update, :on => :update
def run_this_before_update
puts "******** before_save on => :update ********"
# do something
end
Looks like a major reason to use this is the order in which Rails runs the callbacks, check out this most excellent article from pivotallabs http://pivotallabs.com/users/danny/blog/articles/1767-activerecord-callbacks-autosave-before-this-and-that-etc-

Related

Build vs Create in has many through relationship

I wonder if anyone can help me to understand about the difference between build and create in Has Many Through (HMT) relationship?
From my understanding after reading through stackoverflow and google, create is essentially build + save. However, it seems like create does more than just build + save. It also somehow save into the join table of the HMT relationship as shown below.
I created 3 models: user, wiki, and collaborator.
class User < ActiveRecord::Base
has_many :wikis, through: :collaborators
has_many :collaborators
end
class Wiki < ActiveRecord::Base
has_many :users, through: :collaborators
has_many :collaborators
end
class Collaborator < ActiveRecord::Base
belongs_to :user
belongs_to :wiki
end
Then I tested the build and create behavior in rails console:
$rails c
Loading development environment (Rails 4.0.10)
> user = User.first #create instance user
User Load SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> #<User id: 1, name: "Jayzz55">
> (user.wikis.build(title:"hello",body:"world")).save #build and save wiki
SQL INSERT INTO "wikis" ("body", "created_at", "title", "updated_at") VALUES(?,?,?,?) [["body","world"], ["ccreated_at, Sat, 08 Nov 2014 01:39:36 UTC +00:00], ["title","hello"], ["updated_at",Sat, 08 Nov 2014 01:39:36 UTC +00:00]]
=> true
>wiki1 = Wiki.first #create instance wiki called wiki1
=> #<Wiki id:1, title:"hello",body:"world">
>user.wikis
=> #<Wiki id:1, title:"hello",body:"world">
>user.wikis.count
=> 0
>wiki1.users
=> []
>Collaborator.all
=> []
> user.wikis.create(title:"second title",body:"another one")
begin transaction
SQL INSERT INTO "wikis" ("body", "created_at", "title", "updated_at") VALUES (?, ?, ?, ?)[0m [["body", "another one"], ["created_at", Sat, 08 Nov 2014 01:59:39 UTC +00:00], ["title", "second title"], ["updated_at", Sat, 08 Nov 2014 01:59:39 UTC +00:00]]
SQL INSERT INTO "collaborators" ("created_at", "updated_at", "user_id", "wiki_id") VALUES (?, ?, ?, ?) [["created_at", Sat, 08 Nov 2014 01:59:39 UTC +00:00], ["updated_at", Sat, 08 Nov 2014 01:59:39 UTC +00:00], ["user_id", 1], ["wiki_id", 2]]
=> #<Wiki id:2, title:"second title",body:"another one>
>user.wikis
=> [#<Wiki id: 1, title:"hello",body:"world">, #<Wiki id:2, title:"second title",body:"another one>]
>user.wikis.count
=> 1
>wiki2.users
=>#<User id: 1, name: "Jayzz55">
>Collaborator.all
Collaborator Load SELECT "collaborators".* FROM "collaborators"
=> #<Collaborator id:`, user_id:1, wiki_id: 2>
Case wiki1 : build and then save it
The data is not saved into the join table. calling user.wikis does return the object wiki, but running user.wikis.count return 0. Furthermore, running wiki1.users doesn't return the object user. Checking the join table Collaborator returns empty array.
Case wiki2 : create
The data is saved into the join table. calling user.wikis does return the object wiki. Running user.wikis.count return 1. Furthermore, running wiki1.users does return the object user. Checking the join table Collaborator, it does show the relationship clearly mapped.
Seems like Create is not simply just build + new. I'm curious about this behavior and hopefully someone can share their knowledge on this.
I believe that if you had in your first case instead written:
user.wikis.build(title:"hello",body:"world")
user.save
... you would find that ActiveRecord does the "full" save that create also does. The way you've written it, you're asking ActiveRecord to save the newly created Wiki instance, but not the association. So it doesn't create the record in the join table that's required.
Build + Save is equivalent to Create in any relationship

Ruby on Rails Database Niling

So, I have my database, but whenever I call Model.create(:thing => "Hi") it just "does it". When I look at it, my records are All nils! (Minus the ID and Timestamp, those are managed by active record.) Is it the way I create them or IS it my model??? I am using Rails 4.0.1, and it's corresponding active record version. So, what is it? What problems could create this?
My logs:irb(main):003:0>
Email.create(:user => User.find(3), :email => "HIDDEN#HIDDEN.HIDDEN", :key => Email.gen("gemist", "HIDDEN#HIDDEN.HIDDEN"))
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 3]]
WARNING: Can't mass-assign protected attributes for Email: user, email, key
(0.1ms) begin transaction
SQL (2.9ms) INSERT INTO "emails" ("created_at", "updated_at") VALUES (?, ?) [["created_at", Wed, 01 Jan 2014 21:22:24 UTC +00:00], ["updated_at", Wed, 01 Jan 2014 21:22:24 UTC +00:00]]
(1.0ms) commit transaction
=> #<Email id: 3, email: nil, User_id: nil, key: nil, confirmed: nil, created_at: "2014-01-01 21:22:24", updated_at: "2014-01-01 21:22:24">
And my model, incase you are wonder about ZE Generate function.... or anything else
class Email < ActiveRecord::Base
belongs_to :User
def self.gen(user,email)
# Make conf keys on demand
# Salting is used for randominzg and uniquisng, in the case we have already
# sent keys to the same email (We don't want the samekeys more than once!)
# Comboing to make sure that incase of two users who want to confirm the
# same email
salt = BCrypt::Engine.generate_salt
combo = user + email
return BCrypt::Engine.hash_secret(combo, salt)
end
end
I can offer a Migration or schmea if needed.
Now I know under "How to edit" I need to respect the original author, but I have no self respect.
Your question contains the answer already:
WARNING: Can't mass-assign protected attributes for Email: user, email, key
and:
INSERT INTO "emails" ("created_at", "updated_at") VALUES (?, ?) [["created_at", Wed, 01 Jan 2014 21:22:24 UTC +00:00], ["updated_at", Wed, 01 Jan 2014 21:22:24 UTC +00:00]]
You can't set user, email, and key during a mass-assignment (which create() is doing), so it's ignoring those. The only fields being set, as you can see from the INSERT log, are the timestamp fields. So you end up with a fairly empty record in the database.
You can set those fields individually on a model instance, or you can flag them as mass-assignable by putting this in your model:
attr_accessible :user, :email, :key
You may very well want to leave them protected, though. Here's an article on mass-assignment protection. If you're processing form data, you probably want to leave the protection in place for certain fields. If your create() is already using trusted data, you can make them accessible.

Nested Attributes not working with Simple_form, Decent_exposure, Strong_parameters

I am using Simple_form with Decent_exposure, Strong_parameters I have the following setup and I can post to households but nothing gets posted to neighbors
model
class Household < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
has_many :neighbors
accepts_nested_attributes_for :neighbors
view
= simple_nested_form_for household do |f|
= f.input :household_name
= f.simple_fields_for :neighbor, Neighbor.new do |neighbor_form|
= neighbor_form.input :first_name
= neighbor_form.input :middle_name
= neighbor_form.input :last_name
= neighbor_form.input :address
= f.button :submit
Based on the following from my log it looks like the form is working but it is not saving to neighbors the nested model - I have tried both simple_fields_for :neighbor do and simple_fields_for :neighbor, Neighbor.new do hoping that creating a new neighbor would help but it doesn't.
Parameters:{"utf8"=>"✓",
authenticity_token"=>"cVTteqPFa0JMoFi/ys0wAmNIQghubADv5lbPBr6hyq0=",
"household"=> {"household_name"=>"Deew", "neighbor"=>{"first_name"=>"Bill",
"middle_name"=>"", "last_name"=>"Ew", "street"=>"we"}}, "commit"=>"Create Household"}
(0.1ms) begin transactionSQL (0.7ms) INSERT INTO "households" ("created_at",
"household_name", "name", "updated_at") VALUES (?, ?, ?, ?)
[["created_at", Sun, 03 Feb 2013 03:02:56 UTC +00:00], ["household_name", "Deew"],
["name", nil], ["updated_at", Sun, 03 Feb 2013 03:02:56 UTC +00:00]](0.8ms)
commit transaction
Make sure that you include neighbors_attributes in the allowable parameters.

Rails :before_create callback not working for my Model

I'm trying to do the following in my Rails 3 model:
require 'securerandom'
class Contest < ActiveRecord::Base
attr_accessor :key
before_create :generate_key
private
def generate_key
self.key = SecureRandom.hex(3)
end
end
However when I create a Contest, all the fields in my table appear to be correct except the key which remains nil in my DB.
More Info:
In my rails server log, I see the following when I create a contest through my "create contest form"
SQL (0.5ms) INSERT INTO "contests" ("category", "created_at", "description", "key", "price", "status", "time", "title", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["category", "camisas"], ["created_at", Wed, 15 Feb 2012 18:57:16 UTC +00:00], ["description", "test_description"], ["key", nil], ["price", 111], ["status", "In process"], ["time", "2sem"], ["title", "test_contest"], ["updated_at", Wed, 15 Feb 2012 18:57:16 UTC +00:00], ["user_id", 5]]
Note the ["key", nil]
But that should be correct right?, I would guess that the key will be added by the Contest.rb :before_create callback?
Maybe I'm miss-using SecureRandom?
Why are you using attr_accessor? This method is actually going to be defining both a setter and getter method for a key, keeping track of a virtual attribute.
Perhaps you meant to use attr_accessible, but even then that is unnecessary, because you're setting the attribute directly.
Really, you don't need either attr_accessor :key or attr_accessible :key in your model at all if you're just setting it like this. Remove the attr_accessor and it will work.
I think you should write attr_accessible <list accessible params> for example:
attr_accessible :key, :category

Instantiate image in rails 3.1 app then upload to S3 with paperclip

I'm creating a barcode .png in a callback using the Barby gem:
Isbn.rb:
before_save :barcode
def barcode
barcode = Barby::Bookland.new(self.productidentifier_idvalue)
my_bc = Barcodeimg.new(:isbn_id => self.id)
my_bc.image = File.open("#{self.productidentifier_idvalue}-barcode.png", 'w') do |f|
f.write barcode.to_png
end
my_bc.save!
end
Then I'm hoping to upload it directly to S3 via paperclip: here's the Barcodeimg.rb model:
require 'open-uri'
require "aws/s3"
class Barcodeimg < ActiveRecord::Base
has_attached_file :image,
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:s3_protocol => "https",
:path => ":class/:id/:basename_:style.:extension",
:bucket => "bucketname",
:url => ":s3_eu_url"
end
I know the credentials work as I'm using the exact same settings in another model. Any idea what I'm doing wrong? A new Barcodeimg instance is being saved, but without the attachment. There are no error messages - just no image appearing on S3! From the stack trace:
SQL (1.8ms) INSERT INTO "barcodeimgs" ( "created_at", "image_content_type", "image_file_name", "image_file_size", "image_remote_url", "isbn_id", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ["created_at", Tue, 30 Aug 2011 20:26:20 UTC +00:00], ["image_content_type", nil], ["image_file_name", nil], ["image_file_size", nil], ["image_remote_url", nil], ["isbn_id", 7128], ["updated_at", Tue, 30 Aug 2011 20:26:20 UTC +00:00], ["user_id", nil]]
[paperclip] Saving attachments.
File.open will return the result of the block. Which means you're writing the length of the file to your attachment.
I would replace the block with:
f = File.open("#{self.productidentifier_idvalue}-barcode.png", 'w+')
f.write barcode.to_png
my_bc.image = f
Another problem could be the permissions of the current directory. Do you have the rights to write there?

Resources