I'm new to Ruby and Ruby on Rails, coming from a background of C-like languages.
Here is some code that I found in the application_controller.rb file:
def current_user
#current_user ||= Renter.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def authorize_user
redirect_to '/login' unless current_user
end
Here is what I don't understand about it:
- On line 4, is :current_user invoking the current_user instance method, or directly accessing the #current_user instance variable?
- On line 7, is current_user invoking the current_user instance method, or directly accessing the #current_user instance variable?
- On line 2, is :user_id a variable or is it more like a string literal being used as a key? Kind of like in JavaScript one might write session["user_id"] to get theuser_id property of the session object.
class methods aren't relevant in this example - they aren't being used here.
Instance variables will never call methods when they are get/set.
Although the opposite does sometimes happen. It's a very common pattern to create getter/setter methods for instance variables, so common that attr reader/writer/accessor helpers are defined in ruby core. If you write attr_accessor :foo, then foo= and foo will get/set the instance variable.
But this does not happen by default.
To answer your other question:
A symbol :user_id starts with a colon and is similar to a string. The difference between a symbol and a string may seem arbitrary, but it is an important concept in Ruby and making the distinction in your head is a good idea.
To respond to your comment:
Line 4, helper_method :current_user is really something specific to rails, consider it "rails magic" if you like. In effect this is making your current_user method callable from views (whereas by default it would only be available in the controller). :current_user is a symbol which is used to reference the current_user method. Not necessarily something you have to understand in total detail, it would suffice to know that helper_method takes a symbol with the same name as a method and makes that method available to views. As far as I'm aware, it's only relevant to Rails controllers.
It's somewhat common in Ruby to use symbols that refer to method names. It's a more intermediate concept. You can see another example in send:
def asd
return 0
end
class Foo
def instance_method_bar
return 0
end
def self.class_method_bar
return 0
end
end
# how the methods are typically called
asd
Foo.new.instance_method_bar
Foo.class_method_bar
# another way to call them, using send
send(:asd)
Foo.new.send(:instance_method_bar)
Foo.send(:class_method_bar)
I'm not recommending you use send unless you need to, but hopefully it will make it more clear how the symbol :current_user is being used in helper_method
Line 7 is the current_user method being called.
Let's tackle your questions one at a time.
On line 4, :current_user is a method, most likely used to return the current_user, so you can access username, or email, or whatever value the user has.
On line 7, it is still the same method. In this case, Ruby is checking whether a current_user object exists. You can think of unless as if not. So the code will redirect to login if current_user is false, which will happen if current_user == nil. If the user is logged in, current_user != nil, and the redirect does not happen.
On line 2, :user_id is a symbol, and session is a hash, which is key-value pair, such as { a: 1, b: 2 }, and you access the value with the key using the [] method, so session[:user_id] is returning the value of the user_id. In Ruby, you can use anything as the key, symbols are used because they are always unique, the object id of :two and :two is the same, whereas the id is different for "two" and "two".
Related
I'm reading Rails Devise gem documentation and it says:
If the page could potentially not have a current_user set then:
if current_user.try(:admin?) # do something end
I have tried it without question mark
current_user.try(:admin)
and it works the same way returning true or false.
Do I miss something? Is there any difference and how can I see it?
Ruby is somewhat unusual in that it lets you include a wide range of characters in the names of methods including ? and !.
They have no special significance to the interpreter but the language convention is that:
methods ending with ? are interrogative - they should ALWAYS return true or false.
methods ending with ! either mutate the object the are called on or may raise a exception.
So why does it matter at all? In this particular case it does not matter since your user class has an accessor for the #admin instance variable created by ActiveRecord - just like any other column.
If it did not however current_user.try(:admin) would always return nil. Remember that instance variables are always private in Ruby until you provide an accessor*.
# Just a plain old Ruby class - not an ActiveRecord model
class User
def initialize
#admin = true
end
def admin?
#admin
end
end
User.new.try(:admin) # is always nil...
This is because User does not respond to :admin and .try prevents a NoMethodError and just returns nil instead.
ActiveRecord and accessors:
In a plain old ruby class you would add accessors to make the instance variable #admin available:
class User
def initialize
#admin = true
end
attr_accessor :admin
end
Which does this:
class User
def initialize
#admin = true
end
# getter
def admin
#admin
end
# setter
def admin=(val)
#admin = val
end
end
ActiveRecord reads the schema from your database and uses metaprograming to auto-magically add accessors to your model classes. They are a bit more complex than the example above but its the same basic principle. Thats why your User model responds to #admin.
By default, rails ActiveRecord object attributes that are boolean can either be called with or without a question mark (?).
By convention, it is easier to read if you add the ?, and that also shows that it is boolean at first glance.
So, reading this gives the impression that you are asking a question in English.
Therefore, my guess is that admin is a boolean field on the user.
Also, Tom above is very correct.
There is probably no functional difference, in this case.
I'm guessing admin is a boolean field in the users database table. So, user.admin will return either true or false -- no surprises here!
For each column in the table, Rails will also automatically generate an associated method prepended with an ?. For example, if you have a column foo, then there will be a method foo? - which will return true or false depending on the value of foo.
For example, if current_user.name == "Tom" then current_user.name? == true. And if current_user.name == nil, then current_user.name? == false.
It's very rarely necessary to use the ? methods in your code, since all objects are either "truthy" or "falsey" in ruby anyway. But it can sometimes be useful to show intent, and makes the code easier to read, as it's clear that the value is only being used in a boolean manner.
I'm trying to monkey patch ActiveRecord::FinderMethods in order to use hashed ids for my models. So for example User.find(1) becomes User.find("FEW"). Sadly my overwritten method doesn't get called. Any ideas how to overwrite the find_one method?
module ActiveRecord
module FinderMethods
alias_method :orig_find_one, :find_one
def find_one(id)
if id.is_a?(String)
orig_find_one decrypt_id(id)
else
orig_find_one(id)
end
end
end
end
Here's an article that discusses how to actually do what you want by overriding the User.primary_key method like:
class User
self.primary_key = 'hashed_id'
end
Which would allow you to call User.find and pass it the "hashed_id":
http://ruby-journal.com/how-to-override-default-primary-key-id-in-rails/
So, it's possible.
That said, I would recommend against doing that, and instead using something like User.find_by_hashed_id. The only difference is that this method will return nil when a result is not found instead of throwing an ActiveRecord::RecordNotFound exception. You could throw this manually in your controller:
def show
#user = User.find_by_hashed_id(hashed_id)
raise ActiveRecord::RecordNotFound.new if #user.nil?
... continue processing ...
end
Finally, one other note to make this easier on you -- Rails also has a method you can override in your model, to_param, to tell it what property to use when generating routes. By default, of course, it users the id, but you would probably want to use the hashed_id.
class User
def to_param
self.hashed_id
end
end
Now, in your controller, params[:id] will contain the hashed_id instead of the id.
def show
#user = User.find_by_hashed_id(params[:id])
raise ActiveRecord::RecordNotFound.new if #user.nil?
... continue processing ...
end
I agree that you should be careful when doing this, but it is possible.
If you have a method decode_id that converts a hashed ID back to the original id, then the following will work:
In User.rb
# Extend AR find method to allow finding records by an encoded string id:
def self.find(*ids)
return super if ids.length > 1
# Note the short-circuiting || to fall-back to default behavior
find_by(id: decode_id(ids[0])) || super
end
Just make sure that decode_id returns nil if it's passed an invalid hash. This way you can find by Hashed ID and standard ID, so if you had a user with id 12345, then the following:
User.find(12345)
User.find("12345")
User.find(encode_id(12345))
Should all return the same user.
I'm following railstutorial by Michael Hartl. In chapter 8.2.2 he defines a variable #current_user and a method current_user.
app/helpers/sessions_helper.rb looks like this:
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
# Returns the current logged-in user (if any).
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
end
Hartl defines #current_user an instance variable (of User object I guess); How can #current_user be an instance variable if it is itself an instance of the User class?
This is such a good question. It seems that this arrangement is not something that it was planned for, it simply happened: u need to have a variable that has some kind of memory outside the method (scope), in order to compare if #current_user=#current_user, but at the same time the whole arrangement makes theoretically no sense.
This article was written in 2008 and was proof read from members of the rails core team. This paragraph is very telling of the situation:
http://www.railway.at/articles/2008/09/20/a-guide-to-memoization/
--> "A little note on naming here: Some people seem to prefer prefixing the memoizing variable’s name with an underscore to indicate that it’s not meant to be used as an actual instance variable. To be honest, I don’t think this is really necessary unless you define a whole bunch of instance variables and memoized variables."
The SessionsHelper module is mixed into your controllers, so #current_user will be set as an instance variable of the controller which is handling the current request (Rails creates a new controller instance to handle each request)
A common pattern is to use current_user in many places, but check whether it is set.
if current_user
#your code
end
But instead of injecting an if check just about every time you want to use current_user, how and where can you wrap the current_user method in a different method ONCE, so that the you won't have to deal with your code breaking due to a nil value for devise's default current_user method?
The current_user method is added to ApplicationController, then I think you can override it in ApplicationController doing somethig like:
# in application_controller.rb
alias_method :devise_current_user, :current_user
def current_user
if ...#your validation
devise_current_user # || User.new # <-- or whatever other non-nil result
end
end
Creating a custom classes that have the methods you are trying to use on current_user throughout your application is one way.
http://littlelines.com/blog/2013/06/22/how-to-guard-against-ruby-nil-errors/
If you do not want to account for nil value possibilities while working ruby, you're going to be swimming upstream, and become very frustrated. I know I've had similar feelings. It can be really annoying to write:
if current_user && current_user.attr == 'val'
But I've developed ways to make that less awkward over time...
--edit to show some of the things I do to avoid this--
I often do some of these things. This isn't necessarily a recommendation, or a best practice, but I find that it makes my code less verbose and more readable sometimes
If I expect an array, but the array might be nil:
my_array ||= []
That way I can safely do things that require array-ness, usually when this is needed in various places in the scope.
my_array.size
and not have things choke.
In a similar way I might do
car ||= Car.new
Then I can treat car like any car object. But I probably wouldn't do this with current_user, though.
I am going through the great Michael Hartl tutorial to build ruby app here.
I am trying to understand the concept of how to create a session and I am stuck in understanding this line:
self.current_user = user
in this method:
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
end
I understand the whole concept of creating a cookie with the user_token.
But I don't understand what does self.current_user = user means and why is it even necessary to keep this line of code - I have the cookie with the token - why do I need to know the current user?
Also, where does this "self" is being stored - it is not like a flash[:success] parameter I can see in one of my views. so I don't understand where it is.
there are also these 2 methods in the same module:
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
And still I am trying to connect the dots of the purpose for this mysterious current user - is its purpose is to create #current_user global variable to use in the views?
If so - why there are there these 2 duplicated functions def current_user=(user) and def current_user
A few things.
First, you're reading the method names wrong (which is not surprising given how cryptic ruby method naming can be). def current_user=(user) is actually read as defining the method current_user= that takes an argument user, whereas def current_user defines a method current_user that takes no arguments. These are referred to respectively as setters and getters.
Here's a reference: Ruby (programming language): What are setters and getters in Ruby?
So that explains the duplication. On to your next question.
I don't understand what does self.current_user = user means
self is a topic unto itself, worthy of its own discussion, so I won't even try to explain it (here's one reference out of many). For the purposes of this question it's just important to remember that in order to set instance variables, you need to prefix your assignment with self, even within the class (where for other purposes it would be implicit). The rest of the line is a call to the current_user= setter method I mentioned above, with the argument user.
why is it even necessary to keep this line of code - I have the cookie with the token - why do I need to know the current user?
The reason it's necessary is that you don't want to be looking up the user from the token every time you need to get the current user. Take a look at the getter method:
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
What this says is: if I haven't looked up and set the instance variable #current_user yet, then look it up; if I have already set it, then just return it. That saves a lot of looking up.
I think that answers your questions. There are a lot of deeper issues (self, etc.) which you can find more information about elsewhere. Here's one discussion of why you need to include self in setters on SO: Why do Ruby setters need "self." qualification within the class?
UPDATE: Small clarification, that last link about using self for setters within the class is actually a bit off-topic, since you're calling it within a module and not directly from a class. In the context of a module, the self in self.current_user = user will become the class that the module is included inside of, e.g. User.current_user if it was called within the class User, etc. Again, another topic of discussion unto itself...
The method def current_user=(user) is basically a setter that the sign_in method uses in order to set the current_user.
def current_user will return the #current_user or if it is not set it will find it in the Users table by the remember_token. This basically allows you get the current_user at any point in time.
self.current_user in the context of the sign_in method will refer to the calling class or module in this case. It will be calling current_user from the Session Helper module.