I'm trying to learn Ruby and RoR.
I'm confused by Ruby's magic:)
class User < ActiveRecord::Base
require 'digest/sha1'
validates :login, :password, :email, { presence: true }
validates :login, :email, { uniqueness: true }
before_save :hash_password
private def hash_password
if password_chanched?
puts #password.class.name
puts password
password = 'test_pass'
puts password
# #password = Digest::SHA1.hexdigest(password) it works
end
end
end
console:
>> t = User.new login: 'Test', email: 'abc#def.ghi', password: 'trololo'
#<User id: nil, login: "Test", password: "trololo", email: "abc#def.ghi", created_at: nil, updated_at: nil>
>> t.save
NilClass
trololo
test_pass
true
>> t.password
"trololo"
So my question is:
What does password returns? And what have I to modify in callbacks?
Actually, this before_validation callback works just fine:
def downcase_login_and_email
login.downcase!
email.downcase!
end
I think that when I write
password = 'test_pass'
I don't use the setter (like any_instance.password = 'test_pass'), but I create a local variable password which does not make any sense to class instance.
I have to use self:
if password_changed?
puts password # trololo
self.password = 'test_pass'
puts password # test_pass
end
So I have to read more about Ruby:)
Related
I am using Graphql mutation to save a User that looks kinda like this:
class CreateUser < Mutations::BaseMutation
argument :email, String, required: true
argument :password, String, required: true
argument :password_confirmation, String, required: true
argument :first_name, String, required: false
argument :last_name, String, required: false
argument :middle_name, String, required: false
argument :source, String, required: false
field :user, Types::UserType, null: true
field :token, String, null: true
def resolve(args)
user = User.new(password: args[:password], password_confirmation: args[:password_confirmation], email: args[:email])
profile = user.build_profile
profile.first_name = args[:first_name] if args[:first_name].present?
profile.last_name = args[:last_name] if args[:last_name].present?
profile.middle_name = args[:middle_name] if args[:middle_name].present?
user.save!
UserMailer.with(user: user).send_initial_password_instructions.deliver_now if args[:source].present?
# current_user needs to be set so authenticationToken can be returned
context[:current_user] = user
MutationResult.call(obj: { user: user, token: user.authentication_token }, success: user.persisted?, errors: user.errors.full_messages)
end
end
All good here. BUT... I have a model named Contact. Which is quite empty:
class Contact < ApplicationRecord
belongs to :user, optional: true
end
So what I am trying to do is to have a method created on Contact that whenever I create a User I can send some args to Contact and let the Contact method execute the save!
This is what I've been trying: Contact.rb
def self.create_with_user(args)
contact = Contact.new(args)
contact.user = User.new(email: args[:email], password: args[:password], password_confirmation: args[:password_confirmation])
contact.user.save!
end
This is what I've been trying: create_user.rb (graphql mutation)
def resolve(args)
user = User.new(password: args[:password], password_confirmation: args[:password_confirmation], email: args[:email])
profile = user.build_profile
profile.first_name = args[:first_name] if args[:first_name].present?
profile.last_name = args[:last_name] if args[:last_name].present?
profile.middle_name = args[:middle_name] if args[:middle_name].present?
contact = Contact.create_with_user(args)
user.contact = contact
user.save!
UserMailer.with(user: user).send_initial_password_instructions.deliver_now if args[:source].present?
# current_user needs to be set so authenticationToken can be returned
context[:current_user] = user
MutationResult.call(obj: { user: user, token: user.authentication_token }, success: user.persisted?, errors: user.errors.full_messages)
end
But this results into a NoMethodError Exception: undefined method 'to' for Contact:Class.
Newbie rails here so I am really interested in learning this. Thankyou
The error was generated from contact.rb you have a type its belongs_to but you have belongs to.
contact.rb
class Contact < ApplicationRecord
belongs to :user, optional: true
end
Preferred Solutions
If the creation of the Contact should always happen and you don't have any custom params from the endpoint, use a callback on the User model.
user.rb
class User < ApplicationRecord
after_create :create_contact
private
def create_contact
Contact.create(user: self, .....) # the other params you need to pass from model
end
end
If you have custom params from the endpoint you can use accepts_nested_attributes_for and make the contact params nested on the user.
params = {
user: {
user_params,
contact: {
contact_params
}
}
}
user.rb
class User < ApplicationRecord
accepts_nested_attributes_for :contact
end
I have a User model with an account_type attribute that is either "Student" or "Partner". I have created boolean methods in my User model to determine if a user record is either a student or partner (see below).
def student?
self.account_type == "Student"
end
def partner?
self.account_type == "Partner"
end
In rails console, when I set user equal to an instance of User that has a student account type and enter user.account_type == "Student", I get true but when I call user.student?, I get false. Is there an issue with how I've set up these methods? They seem pretty straight forward so I'm not following why true isn't returned for the record.
Console Output:
user = User.last
#<User id: 18, first_name: "gjalrgkj", last_name: "kgjrlgakjrl", email: "terajglrkj#gmail.com", password_digest: "$2a$10$WF.Rw3PzlWilH0X.Nbfxfe5aB18WW6J7Rt4SAKQEwI8...", remember_digest: nil, activation_digest: "$2a$10$/bXG4/nKCiiZHWailUPAmOZj7YhCjKhPm4lUW6nPC3N...", activated: nil, activated_at: nil, reset_digest: nil, reset_sent_at: nil, account_type: "Student", created_at: "2018-07-02 04:21:07", updated_at: "2018-07-02 04:21:07">
>> user.account_type
=> "Student"
>> user.account_type = "Student"
=> "Student"
>> user.student?
=> false
User Model:
class User < ApplicationRecord
has_one :personal_information
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :first_name, presence: true, length: { maximum: 50 }
validates :last_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, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
validates :account_type, presence: true
def User.new_token
SecureRandom.urlsafe_base64
end
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def create_reset_digest
self.reset_token = User.new_token
update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now)
end
def authenticated?(attribute, token)
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
def forget
update_attribute(:remember_digest, nil)
end
def provide_age
now = Time.now.utc.to_date
if self.birthday.nil?
nil
else
self.age = now.year - self.birthday.year - ((now.month > self.birthday.month || (now.month == self.birthday.month && now.day >= self.birthday.day)) ? 0 : 1)
update_attribute(:age, self.age)
end
end
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
def activate
update_columns(activated: true, activated_at: Time.zone.now)
end
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
private
def downcase_email
self.email.downcase!
end
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
User Helper
def account_type
[
['Student'],
['Partner'],
['School Administrator'],
['Philanthropist']
]
end
def student?
self.account_type == "Student"
end
def partner?
self.account_type == "Partner"
end
end
Your 2 methods student? and partner? belong in the user model.
When you are doing user.student? it looks for student? method as a instance method in the user model.
self in helper is not the user instance it points to your helper module.
Hope this helps.
try to put this code into your User Model (not in Helper)
def student?
self.account_type == "Student"
end
def partner?
self.account_type == "Partner"
end
and this is a good idea to create a setter to set the account_type, example
def set_account_type(type)
self.account_type = type
end
I'm new in Ruby. I want to create different users in ruby using iteration.
def createuser(*args)
obj = H['userClass']
obj.login = H['login']
obj.password = a.password = #default_passwd
obj.email = 'test#example.com'
obj.role = MasterUser::ROLE_MASTER_USER
end
For example I want to call this method and send these arguments:
H = Hash["userClass" => MasterUser.new, "login" => admin]
createuser(H)
What is the proper way to implement this?
Here's a modified version. It should bring you closer to your goal, while still being recognizable :
def create_user(parameters)
klass = parameters['user_class']
user = klass.new
user.login = parameters['login']
user.password = #default_passwd
user.email = 'test#example.com'
user.role = klass::ROLE_MASTER_USER
user
end
user_params = {"user_class" => MasterUser, "login" => 'admin'}
new_user = create_user(user_params)
I'd probably do something like this:
class UserFactory
attr_accessor :user
def initialize(klass)
#user = klass.new
end
def create(params = {})
user.login = params.fetch :login
user.password = params.fetch :password, 'default_password'
user.email = params.fetch :email
# user.role should just be initialised on the klass.new call, no need to do that here
# etc...
end
end
class MasterUser
ROLE = 'master_role'
attr_accessor :login, :password, :email, :role
def initialize
self.role = ROLE
end
end
which you would call like:
UserFactory.new(MasterUser).create(login: 'george', password: 'secret', email: 'me#george.com')
The reason I'd use params.fetch :login, instead of just reading it, is that in Ruby accessing a hash by a key that it doesn't have returns nil, while trying to fetch it will throw an error.
For example:
a = {}
a[:foo] #=> nil
a.fetch :foo #=> throw a KeyError
So that is a way of enforcing that the argument hash has the right keys.
I have these questions while reading the Ruby On Rails Tutorial in
here
The validation of User class is:
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
.
.
.
end
In test,while patching a updated user information to the route of the user like this:
def setup
#user = users(:michael)
end
.
.
.
test "successful edit" do
get edit_user_path(#user)
assert_template 'users/edit'
name = "Foo Bar"
email = "foo#bar.com"
patch user_path(#user), user: { name: name,
email: email,
password: "",
password_confirmation: "" }
assert_not flash.empty?
assert_redirected_to #user
#user.reload
assert_equal name, #user.name
email, #user.email
end
The test would pass and only the user's name and email would be updated and password won't change.
If the validation of the password doesn't include the "allow_blank:true",this test would fail.
So I don't understand that: When the test passed which means the password could be blank, why it wouldn't change the password to be blank? How could Rails know I just want to update some of the attributes?
has_secure_password adds a password= setter method method to your model which discards empty? input when setting the password.
irb(main):012:0> "".empty?
=> true
This prevents users from choosing a blank password. If you dont want to take my word for it you can easily test this:
test "does not change password to empty string" do
patch user_path(#user), user: { name: name,
email: email,
password: "",
password_confirmation: "" }
#user.reload
assert_false #user.authenticate("")
end
However what your validation does do is that if the user sets a password it must be over 6 characters:
test "does not allow a password less than 6 characters" do
patch user_path(#user), user: { name: name,
email: email,
password: "abc",
password_confirmation: "abc" }
assert assigns(:user).errors.key?(:password)
end
(PS. this is something that is better tested in a model test than a controller test)
The following User test passes with no problem, the user is valid:
user_test.rb:
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com", callsign: "example",
password: "foobar", password_confirmation: "foobar")
end
test "user should be valid" do
assert #user.valid?
end
end
User model:
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token, :reset_token
has_many :personas, dependent: :destroy
has_secure_password
before_save do
email.downcase!
callsign.downcase!
end
before_create :create_activation_digest
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 }
VALID_CALLSIGN_REGEX = /\A[a-z\d\-.\_]+\z/i
validates :callsign, presence: true,
length: { maximum: 20 },
format: { with: VALID_CALLSIGN_REGEX },
uniqueness: { case_sensitive: false }
validates :password, length: { minimum: 6 }, allow_blank: true
def to_param
callsign
end
.
.
end
However, when I set up exactly the same user in the persona_test, the validation fails. (The persona validation fails too, each User has_many personas)
persona_test.rb:
require 'test_helper'
class PersonaTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com", callsign: "example",
password: "foobar", password_confirmation: "foobar")
#persona = #user.personas.build(name: "Bazman", callsign: "Baz")
end
test "user should be valid" do
assert #user.valid?
end
test "persona should be valid" do
assert #persona.valid?
end
end
Persona model:
class Persona < ActiveRecord::Base
belongs_to :user
before_save do
self.callsign.downcase!
set_persona_id
end
validates :name, presence: true,
length: { maximum: 50 }
VALID_CALLSIGN_REGEX = /\A[a-z\d\-.\_]+\z/i
validates :callsign, presence: true,
length: { maximum: 20 },
format: { with: VALID_CALLSIGN_REGEX },
uniqueness: { case_sensitive: false }
validates :user_id, presence: true
validates :persona_id, presence: true
def to_param
callsign
end
.
.
end
Failed test output:
FAIL["test_user_should_be_valid", PersonaTest, 0.754914]
test_user_should_be_valid#PersonaTest (0.75s)
Failed assertion, no message given.
test/models/persona_test.rb:18:in `block in <class:PersonaTest>'
FAIL["test_persona_should_be_valid", PersonaTest, 0.893247]
test_persona_should_be_valid#PersonaTest (0.89s)
Failed assertion, no message given.
test/models/persona_test.rb:22:in `block in <class:PersonaTest>'
I don't understand why the User validation in persona_test.rb is failing when the setup user is identical to the one in user_test.rb. Are you not allowed to test Users in a Personas test? If so, how do I successfully test personas? Each persona belongs_to a user, so I have to create a user in order to create a persona.
EDIT:
persona_test.rb:
require 'test_helper'
class PersonaTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com", callsign: "example",
password: "foobar", password_confirmation: "foobar")#, activated: true)
#persona = #user.personas.build(name: "Bazman", callsign: "Baz")
#persona.user = #user
#persona.persona_id = 1
end
test "user should be valid" do
assert #user.valid?, #user.errors.full_messages
end
test "persona should be valid" do
assert #persona.valid?, #persona.errors.full_messages
end
end
With the updated persona test above, I get the error message 'User can't be blank'. Why is
#persona.user = #user
not working?
The reason for the failed assertion is that some validations in Persona won't pass:
validates :user_id, presence: true
validates :persona_id, presence: true
The validations are run before saving them to the database. For new records, user_id and persona_id will still be nil.
Because Persona is invalid, the User will be invalid in the other test as well.
In your persona model you have:
validates :user_id, presence: true
validates :persona_id, presence: true
But it doesn't look like a user_id is being set. Try setting it with #persona.user = #user in your test.
Additionally, as a tool for debugging, you can print #persona.errors.full_messages in your test to see where exactly it is not validating.
E.g. assert #persona.valid?, #persona.errors.full_messages
Hope that helps.
EDIT: as per the comments below, the line should actually be #persona.user_id = #user.id. Another way you could achieve the same effect is to actually save the records to the database. So in your setup function, you would use create instead of build. This would, however, be slower.