I have a User model. One of its attributes is a string called :access which can be either nil, "admin", or "active".
Now inside the User model I have the following methods:
def admin?
self.access == "admin"
end
def active?
self.access == "active"
end
They work fine. But if I add attr_accessor :access to the model something breaks. My admin? and active? methods no longer work. When I go into rails console and get a User out of the database I can see that user = User.find(7) shows access is set to "admin". But if I type user.access it returns nil. user.admin? returns false.
attr_accessor is overwriting the default methods of reading and writing an attribute from db. Why do you need attr_accessor if it is already a column in db and Rails provides you with the read and write methods. Are you confusing it with attr_accessible?
Related
I am trying to do an assignment which requires me to create and save an ActiveRecord within my Model class, and then return it. The rspec is expecting to use the find_by method to verify this. Here's my Model:
-----------------------
class User < ActiveRecord::Base
attr_accessor :id, :username, :password_digest, :created_at, :updated_at
after_initialize :add_user
def initialize(attributes={})
#username = attributes[:username]
#password_digest = attributes[:password_digest]
super
end
def add_user
self[:username] = #username
self[:password_digest] = #password_digest
self.save
self[:id] = self.id
end
end
----------------
If I do User.new(params), the record is in fact stored properly to the DB. But, the find_by method is missing for the returned object. So, rspec fails. I have looked everywhere but can't seem to find the solution. I am a noob, so sorry if the answer is obvious and I can't see it.
You say
If I do User.new(params), the record is in fact stored properly to the DB. But, the find_by method is missing for the returned object
This is expected behavior. Hopefully you understand by now the difference between class and instance methods. The main important point is that query methods such as find_by are not made available to model instances. If you do something like user = User.find_by(id: params[:id]), you're calling the find_by class method on the User model.
There are a number of methods like where, order, limit, etc. that are defined in ActiveRecord::QueryMethods - these are made available to ActiveRecord::Relation object and your model class. Most of these methods will return ActiveRecord::Relation objects, which is why they're chainable, e.g.
User.where(params).order(created_at: :desc).limit(5)
However find_by is an exception - it returns a model instance so you can't continue to query on the results. In summary User.new(params) returns an instance of the model which doesn't have find_by available
I'm using ActiveRecord 4.1.8 in a Ruby (not Rails) application. I have a table and a corresponding model that looks like the following:
create_table 'people', :force => true do |t|
t.string 'name'
end
class Person < ActiveRecord::Base
def name=(name)
puts "Attribute setter for name called with #{name}"
write_attribute(:name, name)
end
end
When I create a new instance of Person, I see the Attribute setter for name called with... written to STDOUT. However, when I reload the model instance, I do not see the message written to STDOUT.
p = Person.create(name: 'foobar')
--> Attribute setter for name called with foobar
p.reload
--> <nothing>
The model is getting persisted to the database, so this makes me think name= isn't used when data is loaded into a model from the database. I need to modify certain data attributes when they're read in from the database, so does anyone know what other method I need to override?
From the active_record/persistence.rb source:
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
fresh_object =
if options && options[:lock]
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
else
self.class.unscoped { self.class.find(id) }
end
#attributes = fresh_object.instance_variable_get('#attributes')
#new_record = false
self
end
It just replaces the attributes hash directly. Seems like the easiest way to handle this is to override reload and patch things up after its called.
I have a simple user model
class User < ActiveRecord::Base
has_one :user_profile
end
And a simple user_profile model
class UserProfile < ActiveRecord::Base
belongs_to :user
end
The issue is when I call the following build method, without calling the save method, I end up with a new record in the database (if it passes validation)
class UserProfilesController < ApplicationController
def create
#current_user = login_from_session
#user_profile = current_user.build_user_profile(params[:user_profile])
##user_profile.save (even with this line commented out, it still save a new db record)
redirect_to new_user_profile_path
end
Anyyyyyy one have anyyy idea what's going on.
The definition of this method says the following but it's still saving for me
build_association(attributes = {})
Returns a new object of the associated type that has been instantiated with attributes and linked to this object through a foreign key, but has not yet been saved.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one
Ok, I'm sure experienced vets already know this, but as a rookie I had to figure it out the long way...let me see if I can explain this without screwing it up
Although I was not directly saving the user_profile object, I noticed in my logs that something was updating the user model's last_activity_time (and the user_profile model) each time I submitted the form (the user model's last_activity date was also updated when the logged in user did various other things too - I later realized this was set in the Sorcery gem configuration).
Per http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
AutosaveAssociation is a module that takes care of automatically saving associated records when their parent is saved. In my case, the user mode is the parent and the scenario they provide below mirrors my experience.
class Post
has_one :author, :autosave => true
end
post = Post.find(1)
post.title # => "The current global position of migrating ducks"
post.author.name # => "alloy"
post.title = "On the migration of ducks"
post.author.name = "Eloy Duran"
post.save
post.reload
post.title # => "On the migration of ducks"
post.author.name # => "Eloy Duran"
The following resolutions resolved my problem
1. Stopping Sorcery (config setting) from updating the users last_activity_time (for every action)
or
2. Passing an ':autosave => false' option when I set the association in the user model as follows
class User < ActiveRecord::Base
has_one :user_profile, :autosave => false
end
I am following Michael Hartl's RoR tutorial, and it is covering the basics of password encryption. This is the User model as it currently stands:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email,: password, :password_confirmation
email_regex = /^[A-Za-z0-9._+-]+#[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+[A-Za-z]$/
#tests for valid email addresses.
validates :name, :presence => true,
:length => {:maximum => 50}
validates :email, :presence => true,
:format => {:with => email_regex},
:uniqueness => {:case_sensitive => false}
validates :password, :presence => true,
:length => {:maximum => 20, :minimum => 6},
:confirmation => true
before_save :encrypt_password
private
def encrypt_password
self.encrypted_password = encrypt(password)
end
def encrypt(string)
string
end
end
I posted a previous question about before_save not working, and it turns out that what I had accidentally done is written my encrypt_password as:
def encrypt_password
#encrypted_password = encrypt(password)
end
I understand that if self.encrypted_password sets the encrypted_password attribute, but why does #encrypted_password not do that as well? In the response to the previous post about before_save not working someone said that the instance variable was "forgotten" after the method ended with the way I had originally coded it -- why was this the case? Can someone please explain how self and # work differently in the context of the code above?
NOTE: I already took a look at the posts here and here, but they both say that "self" is calling the attribute = method, and I don't even understand how that method could exist here since I never created it or declared the encrypted_password w/ attr_accessor. So I am still confused, and this is not a re-posting of those questions.
The accessors for encrypted_password have been automatically added by Rails for you because a field by that name exists in the users table.
Any field you add to a table will be automatically made available via self.field_name.
Here is where Michael Hartl's tutorial creates the encrypted_password field in the users table.
Also look at the user_spec.rb (Listing 7.3) in the linked page, where the author is testing for the presence of the encrypted_password field.
UPDATED:
As #mu points out, the # is used for Ruby instance variables (aka "iv"). But encrypted_password is an "attribute" defined by Rails, and is not an instance variable.
If you run User.find(1).instance_variables, you will see that there is an iv called #attributes, which is of type Hash.
Inside that iv is where the encrypted_password is stored. Rails has defined accessor methods for encrypted_password, which gets/sets the data for that
attribute in the #attributes Hash.
Note that you could also get/set the data via #attributes["encrypted_password"] called from within the User class (but the accessor methods are convenient way to do just that).
If you let me, I'd like to rephrase the answer.
I explained in this post, that as soon as you create a (rails-) Model with the same (singular) name as one of the (plural) tablenames of your database, the "magic" of rails will create setters and getters in order to modify your table's records.
This is because your model inherits all methods from the ActiveRecord::Base Class, which defines basic CRUD accessors (Create, Read, Update, Delete).
The key point related to your question, is that you don't know how rails implements the instance variable related to your database table column, And you shouldn't. :) All you have to know is that at that point, you have setters and getters available to CRUD (create, read, update, delete) your database column "encrypted_password".
In your example, maybe rails uses an instance variable called #encrypted_password, maybe rails uses an hash-instance-variable called #attributes["encrypted_password"], or maybe rails uses an instance variable called #you_will_never_guess_encrypted_password.
-
And that's a good point you don't know about the internal rails behavior with instance variables. In 2019 Rails further development may lead the framework to use #complicated-hash-instance-variable to store the encrypted_password value.
In fact the best approach is to let rails manage its "private" "affair" ;) with instance variables, and just use the getter and setter methods it provides to you.
So your application will still work with encrypted_password in the next century (I hope so ^^).
So if you use #encrypted_password it may work with some "imaginary" version of rails and it won't work anymore with other rails versions. Actually with a current version of rails it doesn't work.
-
The second key point is that when you want to use the getter "encrypted_password" Rails created for your encrypted_password database table column, you prefix it with "self" in order to tells Ruby : "ok I want to use the encrypted_password method of my User instance variable."
In Ruby, a method is called by passing its name to a receiver.
You write it like this :
my_receiver.my_method
In your case we pass the method encrypted_password to the User instance variable. But we don't know how this instance variable will be named, so we use the word self to tell Ruby : "I'm talking about any instance variable of the User class that calls the encrypted_password method".
For instance we could have named our instance variable "toto" :
toto = User.new
so toto.encrypted_password would display the encrypted password, and self in this very case in our code would reference toto.
However, thanks to Ruby, if you don't give any receiver when calling a method, Ruby will assume you pass it to self.
Reference : Pragmatic Programmer's guide
So in your example, you even don't need to put "self." as prefix.
You could have it written like this :
class User < ActiveRecord::Base
def encrypt_password
encrypted_password = encrypt(password)
end
end
I hope this helps to clarify this interesting subject.
TL;DR -
Always write self.widget_count = 123 if you intend to save widget_count back to the database.
(But please do read the long answers, as the reason why is valuable to know.)
we have model helper (used by several different models) called set_guids that sets self.theguid to a random string. Been using it for a long time, we know it works.
in a new model 'Dish' we created, we have
before_create :set_guids (NOTE: no other before/after/validation, just this)
def do_meat_dish
( this is invoked by #somemeat.do_meat_dish in the Dish contoller )
( it manipulated the #somemeat object using self.this and self.that, works fine)
( THEN sometimes it creates a new object of SAME MODEL type )
( which is handled differently)
#veggie = Dish.new
#veggie.do_veggie_dish
end
def do_veggie_dish
recipe_str = "add the XXXX to water"
recipe_str.gsub!("XXXX", self.theguid) *** the PROBLEM: self.theguid is nil
end
as soon as we execute veggie = Dish.new shouldn't veggie.theguid be initialized?
Note we have not saved the new object yet... but the before_create should still have done its thing, right?
it is something to do with create a new instance of a model inside a method for the same model?
is it something with using # for the variables?
Additional note: if we comment out the line trying to access self.theguid everything else works fine ... it's ONLY the value (supposedly) set by the before_create set_guids that is nil instead of being a guid.
before_create is called only before the object is saved to the database the first time. That's why you get nil.
I suggest that you use after_initialize callback instead. Be careful though, since after_initialize will be called whenever the document is new or loaded from the db, that way you will have new guids every time you get the document, which is not what you want. So I suggest you do something like:
def set_guids
return unless theguid.nil?
.....
end
As another solution, if you don't want to change the after_create callback above, you can do something like:
def theguid
super || set_guids
end
That should let you go also.