After callback doesn't work - ruby-on-rails

There is User model, and when user creates account, letter with link for confirmation is sent to his email adress. after_callback sets user's confirmed column to false.
(I don't sure about if more than one callbacks of one type is possible, there are two after callbacks)
User.rb:
after_create :confirmation
after_create :add_user_profile
...
def confirmation
if self.guest
self.confirmed=true
elsif self.authentication
self.confirmed=true
else
self.confirmed=false
begin
confirmation_token = SecureRandom.urlsafe_base64
end while User.exists?(:confirmation_token => confirmation_token)
self.confirmation_token=confirmation_token
self.confirmation_link_sent_at=Time.now.utc
UserMailer.send_confirmation_link(self).deliver
User.delay.delete_unconfirmed(self)
end
end
But when I output last user from console:
>> User.last
User Load (1.0ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
#<User id: 21, email: "somddething#mail.com", password_digest: "$2a$10$LMtsumUaS5MaiRhz3.mZ2em7Fbt3.66pbpe4863zj3b0...", created_at: "2013-05-22 17:17:18", updated_at: "2013-05-22 17:17:18", guest: false, auth_token: "lbIJftEqgwyXYymjEVdhCQ", password_reset_token: nil, password_link_sent_at: nil, confirmed: nil, confirmation_token: nil, confirmation_link_sent_at: nil>
As you can see, confirmed is nil, also others related columns too. Why?

That's not working because your are setting the confirmed attribute to the users but you are not saving it again.
Depending on your needs, you can change the callback to before_create or save the user:
def confirmation
...
self.save
end
Hop this helps!

Related

Ruby on Rails - field not recognized by ActiveRecord relation

I have a User table for my app which contains the list of all users. This table has a Boolean field named active.
I have this code to fetch the user:
existing_user = User.where("LOWER(email) = ?", auth_hash['info']['email'].downcase)
And this is what I get when I do an existing_user.inspect:
User Load (1.9ms) SELECT "users".* FROM "users" WHERE (LOWER(email) = 'biswanath.chandramouli#gmail.com')
#<ActiveRecord::Relation [#<User id: 4, name: "Biswanath Chandramouli", email: "Biswanath.Chandramouli#gmail.com", last_updated_by: "", admin: true, active: true, last_updated_on: nil, created_at: "2018-10-30 08:14:59", updated_at: "2018-10-30 08:14:59"
As you can see, existing_user has the property active available as shown above.
But this code fails:
if(!existing_user.active?)
The above call throws this error:
undefined method `active?' for #<User::ActiveRecord_Relation:0x00007f0a58b2c500> Did you mean? acts_like?
When existing_user.inspect shows active: true, why does the above call existing_user.active fail? Pls help!
I think you should use if(!existing_user.first.active?). This will work in your case. Where clause returns you an array, not an object. In your case, existing_user is an array not an object.
This answer is off-topic but could save you a lot:
Every time you call this existing_user = User.where("LOWER(email) = ?", auth_hash['info']['email'].downcase), it's going to downcase all the emails in the table and look for the correct one.
I would suggest to downcase the email before saving the user and add an index on it;
before_save { self.email = self.email.downcase }
and then get the user:
user = User.where(email: auth_hash['info']['email'].downcase).first
Try this method and you'll see a big difference in the data retrieval (which is now 1.9ms)

NoMethodError in UsersController#show after updating users attributes

I am using the railstutorial.org book.
I tried updating the user attributes as written in Chapter 7 of the book, but the email became nil. I have tried updating to no avail. This produces a NoMethodError in UsersController#show: undefined method `downcase' for nil:NilClass.
Here is the show.html.erb
<% provide(:title, #user.name) %>
<h1>
<%= gravatar_for #user %>
<%= #user.name %>
</h1>
Users helper
module UsersHelper
# Returns the Gravatar of the given user.
def gravatar_for(user)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
User Controller
class UsersController < ApplicationController
def new
end
def show
#user = User.find(params[:id])
end
end
User model
class User < ActiveRecord::Base
before_save { email.downcase! }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
end
Please I need help to fix this. Thanks.
I'm also following this tutorial, so I don't pretend to be a Rails expert.
That said, I just went back and reviewed the tutorial material surrounding the first introduction of the
before_save { email.downcase! }
syntax, and I see this at the end of Chapter 6 (listing 6.42).
I'm pretty sure this isn't working for you because your UsersController is missing a definition of the "New" method:
def new
#user = User.new
end
I'm wiling to bet that your #user object is Nil because you haven't create an instance of it yet. BTW, at this point in the tutorial, you should have also defined a Create method in UsersController.
EDIT: If your problems are limited to what is happening in the Rails console, I agree with the comments that you need to provide a complete transcript of your console session in order for folks to provide a complete answer.
Here's an example Rails console session from within my Rails Tutorial project:
Invoke the console and make it aware of my User model:
$rails console
Loading development environment (Rails 4.2.2)
require './app/models/user'
=> true
Create a User instance named "spong"
**spong = User.new**
=> <User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_digest: nil, admin: nil, activation_digest: nil, activated: false, activated_at: nil>
(Note: My User model has more attributes because I am toward the end of the Tutorial.)
Populate values for name and e-mail:
spong.name = "Yo Dawg!"
=> "Yo Dawg!"
spong.email = "YoDaWG#dawg.COM"
=> "YoDaWG#dawg.COM"
Note that my initial e-mail address is mixed case.
Invoke the downcase method:
spong.email.downcase
=> "yodawg#dawg.com"
This is working for me in the console. Now let's try the update_attributes method:
spong.update_attributes(name: "The Dude", email: "dude#AbideS.org")
This is straight out of the tutorial, but it doesn't work for me, because at this point in my journey, I have implemented features that prevent this kind of update:
(6.5ms) begin transaction
User Exists (0.5ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('dude#AbideS.org') LIMIT 1
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('dude#AbideS.org') LIMIT 1
(0.1ms) rollback transaction
=> false
As Hartl says:
Note that if any of the validations fail, such as when a password is required to save a record (as implemented in Section 6.3), the call to update_attributes will fail.
So let me try the singular version of this command:
spong.update_attribute( :email, "dude#AbideS.org")
(3.7ms) begin transaction
SQL (4.0ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "activation_digest") VALUES (?, ?, ?, ?, ?) [["name", "The Dude"], ["email", "dude#abides.org"], ... ]
(1.2ms) commit transaction
==> true
spong.email
=> "dude#abides.org"
Not that the e-mail address is already converted to lower case in the INSERT command--exactly as expected, thanks to that
before_save { email.downcase! }
we have in our User model.
But what's with all the DB activity? This is because update_attributes updates a single attribute and saves the record without going through the normal validation procedure (which is why I am able to do this). While research this, I found this excellent discussion about update_attribute and update_attributes. Great stuff!
OK, so what happens if we try to call update_attribute when the (existing) e-mail address is blank? Let's see:
newUser = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_digest: nil, admin: nil, activation_digest: nil, activated: false, activated_at: nil>
Everything in newUser is nil. Let's try to update the e-mail address:
newUser.update_attribute(:email, "cOnFuSed#MixecCase.com")**
(1.2ms) begin transaction
SQL (3.9ms) INSERT INTO "users" ("email", "created_at", "updated_at", "activation_digest") VALUES (?, ?, ?, ?) [["email", "confused#mixeccase.com"], ...]
(0.9ms) commit transaction
=> true
Again, because of the behavior of update_attribute/update_attributes, my database is updated; somewhat counterintuitively, a record is inserted during this "update" process, but this is because I had not yet saved this (or the first) record to the DB.
I hope all this helps. At a minimum, I have demonstrated that this DOES work via the console--even with previously 'nil' values (and I learned a ton while doing the research to attempt an answer).

Rails 4. Strange behavior when working with before_validation callback

Suppose there are users records in the database. And we decided to add validation in model. Model:
class User < ActiveRecord::Base
validates_format_of :name, with: /\A[^\d]*\z/, allow_blank: true
before_validation :delete_digits_from_name
def delete_digits_from_name
self.name = name.gsub!(/\d/, '')
end
end
Scenario 1 in console:
User.create(name: 'Username 15')
User.last
=> #<User id: 14154, name: "Username"
And it's ok. But there are old record (created before adding validation) and.. scenario 2:
user = User.first
=> #<User id: 1, name: "Username 15"
user.save
=> true
user
=> #<User id: 1, name: "Username"
user.reload
=> #<User id: 1, name: "Username 15"
But why?? Why changes not saved?
The gsub! in delete_digits_from_name changes the name in place, so Rails thinks name is the same thing it loaded from the DB. It's the same object, even though you've changed its value. Rails does this to optimize away DB updates when no data has changed, and in-place editing confuses it.
Switching to self.name = self.name.gsub(/\d/, '') (no !) assigns a new String that Rails will recognize as dirty and needing saving.
You can also add name_will_change! after your gsub! to tell Rails the attribute needs saving.

Rails 4, make attribute inaccessible

previous rails 4 I had in a model
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
...
end
But now strong_parameters replaced the protected_attributes so I comment it and use permit.
Now I discovered that I can access attribute without permitting it.
In rails c I manage to do this:
2.0.0p247 :002 > User.new(admin: "1")
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_token: nil, admin: true>
2.0.0p247 :016 > user = User.new(name: 'Nir', email: 'nir#example.com', password: 'foobar', password_confirmation: 'foobar', admin: "1")
=> #<User id: nil, name: "Nir", email: "nir#example.com", created_at: nil, updated_at: nil, password_digest: "$2a$10$xVnY8ydd5SoaLVipK5j4Del40FrOmu4bKypGjBEwvms7...", remember_token: nil, admin: true>
When obviously I should not be able to set and change the admin attribute. Only user.toggle(:admin) should be able to.
So what am I not understanding or should do right.
And how to make this test pass:
describe "accessible attributes" do
it "should not have allow access to admin" do
expect do
User.new(admin: "1")
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
To prevent a user from setting the admin property, you should not add it as a parameter of the permit method.
params.require(:user).permit(:name, :whatever_else_you_allow)
Keywords in this are: params (it deals with parameters) and permit (you tell rails which properties to allow for).
Strong Parameters will make Action Controller parameters forbidden to be used in Active Model mass assignment until they have been whitelisted. In your test however, you set the property directly on the model. Nothing prevents you from doing that.

Is there a way to use mass assignment for everything except for one param in Ruby on Rails?

I am trying to lowercase params[:user][:email] in my application but currently I'm using #user = User.new(params[:user]) (which includes the email) in my def create. Is it possible to allow for mass assignment for everything except for a single item?
I know I could just not use mass assignment but I was wondering if this was possible.
Yes.
class User
attr_protected :email
end
Here's how you'd use it:
user = User.new(params[:user])
user.email = params[:user][:email].downcase
If you want to downcase the email attribute automatically though, you can simply override the email= method, which I strongly recommend:
class User < ActiveRecord::Base
def email=(other)
write_attribute(:email, other.try(:downcase))
end
end
Loading development environment (Rails 3.2.5)
irb(main):001:0> User.new({:email => 'Me#you.com'})
=> #<User id: nil, email: "me#you.com", username: nil, created_at: nil, updated_at: nil>
irb(main):002:0> User.new({:email => nil})
=> #<User id: nil, email: nil, username: nil, created_at: nil, updated_at: nil>
you should look at attr_protected. This lets you define only the attributes you want to prevent from being mass assigned.
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html#method-i-attr_protected

Resources