Why do we need question mark in this rails try block? - ruby-on-rails

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.

Related

Is it possible to pass a nested property of a hash to function in ruby

I have this function in rails controller:
def validate_params(*props)
props.each do |prop|
unless params[prop].start_with?('abc')
# return error
end
end
end
im thinking if I have params[:name] and params[:bio] and I want to validate name & bio with this function (not every attribute I might want to validate), I will call it with validate_params(:name, :bio). But, for nested param it won't work like params[:user][:name]. Is there anything I can do to pass this nested property to my function or is there a completely different approach? Thanks
Rails Validations generally belong in the model. You should post some additional info about what you're trying to do. For example, if you wanted to run the validation in the controller because these validations should only run in a certain context (i.e., only when this resource is interacted with from this specific endpoint), use on: to define custom contexts.
If you don't want to do things the rails way (which you should, imo), then don't call params in the method body. i.e.
def validate_params(*args)
args.each do |arg|
unless arg.start_with?('abc')
# return error
end
end
end
and call with validate_params(params[:user], params[:user][:name]
but yeah... just do it the rails way, you'll thank yourself later.

Understanding class variables and methods in Ruby on Rails

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".

Monkeypatch ActiveRecord::FinderMethods

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.

Use regular attribute assign and save or use update_attribute?

I recently 'discovered' the update_attribute method. So, I started changing sequences like
self.attribute = "foo"; save
in model or controller methods by
self.update_attribute(:attribute, "foo")
Now, the more I'm doing this, the more I'm wondering whether this is "good practice", and whether this method was intended to be used this way.
Any input from the "pro's" on this?
I would suggest using update_attribute for flags or any update operation that does not need validations since it does not fire validations. From rails documentation we can read:
Updates a single attribute and saves the record without going through
the normal validation procedure. This is especially useful for boolean
flags on existing records. The regular update_attribute method in Base
is replaced with this when the validations module is mixed in, which
it is by default.
Whereas update_attributes does:
Updates all the attributes from the passed-in Hash and saves the
record. If the object is invalid, the saving will fail and false will
be returned.
Let's look at the code now:
def update_attribute(name, value)
send(name.to_s + '=', value)
save(false)
end
def update_attributes(attributes)
self.attributes = attributes
save
end
It's always better to use update_attribute, or update_attributes if you need to update a single instance with simple data, as you can read "UPDATE" and know that you are "UPDATING".
You must know also that there is a method called update_column, that does 'kinda' the same stuff, but, update_column does NOT update the updated_at timestamp on the database.
Also, if you need to edit a large amount of instances/rows in the database with the same value, you have a method called update_all. Here is an example
#instances = Instance.all
#instances.update_all(:attribute, value)
and that will update all the attributes of that table. You will find this usefull after doing werid migrations.
Besides all of this, you can always use the 'save' way, I strongly recomend this when you have to calculate a lot of data to update a single instance. Here is an example:
#BAD
def updater_method
foo = Bar.first
foo.update_attributes(attr_one: some_calcule_method, attr_two: some_other_calcule_method, attr_three: some_more_calcule_method)
end
#GOOD
def saver_method
foo = Bar.first
foo.attr_one = some_calcule_method
foo.attr_two = some_other_calcule_method
foo.attr_three = some_more_calcule_method
etc
foo.save!
end
This will help you in debbuging, so if any method fails, you can see it clearly, with the line number and all that stuff.
Regards, Lucas.

Simple boolean data update with mongdb?

I am using Rails and mongoid to work with mongodb.
Usually in rails when working with Active:Record, you have access to the method .toggle! which simply allows you to invert the value of a boolean field in your db.
Unfortunately this method is not available for mongoDB:
user = User.first
user.toggle!(:admin)
NoMethodError: undefined method `toggle!' for #<User:0x00000100eee700>
This is unfortunate... and stupidly enough I don't see how to get around without some complicated code...
Any suggestion on how to achieve the same result concisely ?
Thanks,
Alex
ps: also one of the problems is that when I want to modify the field, it goes through validation again... and it's asking for the :password which I don't save in the db, so:
User.first.admin = !User.first.admin
won't even work :(
The issue here is specifically mongoid, not mongodb. toggle! is a part of ActiveRecord::Base, but fortunately it's not hard to replicate.
def toggle!(field)
send "#{field}=", !self.send("#{field}?")
save :validation => false
end
Add that into your model (or add it into a module, and include it in your model), and your Mongoid models will gain functionality equivalent to what you're used to in AR. It will read the field's value, invert it, write it (through the setter, per the toggle! documentation), and then save the document, bypassing validation.
# Get object's boolean field and toggle it
# #param [Object] mongoid object
# #param [String, Symbol] flag
# #example
# foo = User.find('123')
# toggle_flag!(object: foo, flag: :bar)
def toggle_flag!(object:, flag:)
object.update(flag => !object[flag])
object.save!
end
Ok the validation did not work because of a type, the code should be:
save :validate => false (not :validation)

Resources