Is an instance variable referring to an instance of the class? - ruby-on-rails

I'm fairly confused and would appreciate some help.
I'm going through the Michael Hartl tutorial and the most complicated aspect (for me) is simply understanding what I perceive to be inconsistencies in syntax (I know I'm wrong in this regard and it's just my perception).
I'm currently on chapter 8, however a simple example from earlier on:
def show
#user = User.find(params[:id])
end
And another:
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
As far as I understand instance variables, they are supposed to be attributes of the object or instance of the class, eg, :name, :email, :password_digest, etc.
In these examples, we haven't written an initialize method for the class as we simply generated a migration with 4/5 columns (which correspond with the attributes specified); the columns in this migration are, as I understand, interpreted by Rails as attributes or instance variables of the instance of the class (the object?)
What then is #user or rather why is an instance variable used in this context? It isn't an attribute (eg, :name) of an instance of the class, but rather appears to be a reference, placeholder or representation of an instance of the class?
n.b. I understand (well, as far as beginners understand) what the code above does, ie, queries the User model - which is also called User - to retrieve a record, however I don't understand why this is being assigned to an instance variable or in what contexts to use instance variables, ie, I thought they were for specifying attributes for an instance of the class, not for operating as a placeholder or referencing an instance of the class (including its attributes).
EDIT: I think my confusion emanates from instance variables being used for both specifying attributes and for, as per Mark's definition below, 'operating as a container for all the attributes of a class.'
The above examples appear to use instance variables as a 'container for attributes', whereas other examples I've read use instance variables to store attribute values.
An example from earlier in the book:
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
I guess it's both?

From rubyist.net:
An instance variable has a name beginning with #, and its scope is confined to whatever object self refers to. Two different objects, even if they belong to the same class, are allowed to have different values for their instance variables.
So to answer your question: Yes, an instance variable is referring to an instance of a class and is not the same thing as an attribute of a class. More accurately, an instance variable is simply a container for all the attributes of a class (in this case the User class). Whenever you use the #user syntax, you're saying that you want to create your own instance of a user, with separate values for the user's attributes than say, another instance variable.
I could create two instance variables like this:
#user1 = User.new
#user2 = User.new
Now, I can assign attributes to the users separately like this:
#user1.email = "test#exmample.com"
#user2.email = "anotheremail#example.com"
Each instance of this user is unique, and that is the core concept behind instance variables.
For rails, however, any instance variables you declare in your controller will be accessible in the corresponding view, which is why you can access the #user variable in, say, your new.html.erb file.
EDIT
Here's the code you updated your post with:
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
The initialize method is actually the method that gets called when you create a new object. For example if I created a new user object, I'd do it like this:
#user = User.new
By default, the initialize method for the User class will get called when I do this. Also, you'll notice that the initialize method takes one parameter called attributes. The parameter is defined like this:
def initialize(attributes = {})
This means that if you pass in any arguments to the new method, those arguments will be passed into the attributes parameter. the = {} simply means that if you don't pass in any arguments to the new method method, the attributes parameter will default to a blank hash.
For example, I could initialize a user object like this:
#user = User.new(name: "mark", email: "test#example.com")
the name and email parameters would be passed into the initialized method via the attributes hash. then, your code suddenly makes more sense. This line:
#name = attributes[:name]
simply assigns the name you passed into the new method to be the value of #user.name. So in this sense, the above line is confusing because it appears to be assigning an attribute to an instance variable, but it's really just assigning the value of the name attribute on the #user instance variable.
Therefore, once the initialize method returns, the #user variable would have access to the attributes you assigned in the initialize method.
More reading:
good stack overflow question
object initialization in ruby

Related

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

How do I initialize a new instance of a class when I call the AREL association the first time?

I inherited a project - controller has a custom action within which we set a couple instance variables:
#contact = #property.contact ? #property.contact : Contact.new
#contractor = #property.contractor ? #property.contractor : Contractor.new
I think the Rails way would be to initialize Contact.new and Contractor.new when I call #property.contact or #property.contractor if they don't exist instead of setting these up as instance variables in the controller.
But I don't know the syntax to do it.
Without seeing the rest of your app, it's far more likely that this logic belongs in the controller, where it is. Usually these things are done to pre-populate the association objects required for Rails to render a form correctly, so it's not something that really makes sense in the model itself, ie. for a property.contact method to return a Contact.new if one doesn't already exist doesn't (usually) make any logical sense, whereas setting the #contact variable in the controller/view to a new one for display purposes makes sense.
FWIW, the above code is precisely the same as the more concise:
#contact = #property.contact || Contact.new
#contractor = #property.contractor || Contractor.new

Adding a parameter to an instance variable in Rails

I am creating a instance variable that gets passed to my view. This variable 'post' has a user_id associated with it and I wanted to add an extra attribute called 'username' so I can also pass that and use it in the view.
Here is an example of what I would like to do.
#post = Post.find(params[:id])
#post.username = User.find(#post.user_id).username
A username column does exist on my Users model but not my Songs model. So it won't let me use
#post.username
I know I can just make an entirely new instance variable and put that information in there but I would like to keep everything nice and neat, in one variable. Which will also make my json rendered code look cleaner.
Any ideas on how I can accomplish this?
Thanks!
Based on the presence of a user_id in your Post model, you probably already have an association set up that can retrieve the username. It will probably save a lot of trouble to simply use the existing association:
#post = Post.find(params[:id])
username = #post.user.username
If you're likely to be querying more than one post at a time (e.g., on an index page, calling .includes to tell Rails to eager-load an association will help you avoid the N+1 problem:
#posts = Post.includes(:user).all
Finally, to include the associated record in your JSON output, pass the :include parameter as you serialize:
# in controller
render :json => #post.to_json(:include => :user)
This question includes a much more comprehensive discussion of serialization options. Well worth a read.
No need to pass a separate instance variable.
1. You can use #post.user.username in view itself.
2. Or you can create a helper and pass #post.user
def username user
user.username
end

Ruby (Rails) string manipulation upon create

I'm working on a twitter-like app for practice. Users create posts, and I'm adding functionality so that users can tag other users in their posts by putting #email of the user they want to tag at the beginning of their post.
in_reply_to is the id of the user being tagged in the Micropost.
This is in my Micropost_controller.rb
#reply_regex = /(\A#[^# ]+(#)\w+(\.[a-z]{2,3}){1,2}.*\z)/i
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.content =~ #reply_regex
email = #micropost.content.scan(/([^# ]+(#)\w+(\.[a-z]{2,3}){1,2})/i).first.first
#micropost.in_reply_to = User.find_by_email(email).id
end
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_path
When I run the email-extracting part on a string in the console it works perfectly and returns the email. But when I create new microposts, the in_reply_to always stays nil.
Something like this:
class C
#a = 11
end
does not create an instance variable named #a for instances of C. When you hit #a = 11, self will be the class itself so #a will be an instance variable for the the object C. If you put the above into irb and look at C.instance_variables, you will see this:
>> C.instance_variables
=> [:a]
but when you look at an instance of C:
>> C.new.instance_variables
=> []
Also, instance variables are automatically created on first use and initialized to be nil.
Combining the above tells us that you have a #reply_regex instance variable in the class object MicropostController but not in the instances. Your def create is an instance method so it will use the #reply_regex instance variable; but you don't have #reply_regex as an instance variable for MicropostController objects so it will be created and initialized as nil inside your if statement. The result is that your if ends up being this:
if #micropost.content =~ nil
and #micropost.content =~ nil will evaluate to nil and, since nil is false in a boolean context, the if block is never entered and #micropost.in_reply_to is never assigned a value.
You can use a class variable for your regex:
##reply_regex = /(\A#[^# ]+(#)\w+(\.[a-z]{2,3}){1,2}.*\z)/i
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.content =~ ##reply_regex
#...
as class variables are visible to instance methods or, better, just use a constant:
REPLY_REGEX = /(\A#[^# ]+(#)\w+(\.[a-z]{2,3}){1,2}.*\z)/i
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.content =~ REPLY_REGEX
#...
As an aside, I think you should move the reply-to checking into your model. You could use a before_validation callback to strip off any leading and trailing whitespace in content and extract and save the reply-to:
class Micropost < ActiveRecord::Base
before_validate :process_content, :if => :content_changed?
#...
private
def process_content
# Strip leading/trailing whitespace from self.content
# Extract the reply-to from self.content and save it in self.in_reply_to
end
end
The advantage here is that if the content is changed or created without going through your controller (such as a migration, by hand in the Rails console, some system task that is notifying the users of something, ...), you still get everything done.

In what circumstances should I use instance variables instead of other variable types?

I am using Ruby on Rails 3 and I would like to know in what circumstances should I use instance variables instead of other variable types and if there are security reason for those.
Example:
# Using an instance variable
#accounts = Account.find(...)
# Using a "local"\"normal" variable
account = Account.find(...)
In general an instance variable is local and persisted inside an instance of an object, whereas a local variable is only local and persisted inside a function/object/block scope. For instance:
class User
def name
#name
end
def name= name
#name = name
end
end
def greet user
name = user.name || 'John'
p "Hi, my name is #{name}"
end
user = User.new
greet user
=> 'Hi, my name is John'
name
=> NameError: undefined local variable or method 'name' for main:Object
user.name = "Mike"
greet user
=> 'Hi, my name is Mike'
#name
=> nil
In the greet function name is a local variable that is only defined within that function. The name variable is set on the first line on the function, name = user.name || 'John', but its value is not persisted outside of the function. When you try calling name you get a NameError because name has only been defined as a local variable within the greet function.
#name is local to the user instance of the User class. When you try calling it outside of that context you get nil. This is one difference between local and instance variables, instance variables return nil if they have not been defined, whereas local non-instance variables raise an Error.
Notice that both variable types are local to a specific context though. #name is defined within the user instance, so when you call user.name you are calling the name function in the user instance, in which #name is defined. name is only defined in the greet function, so when you call p "Hi, my name is #{name}" you are able to get a value for name because you are within the scope in which it is defined.
#Pan's answer gives a good explanation of the difference between them.
In general (in pretty much any language) you want to define variables in the smallest scope required. So if you don't need something to be persisted across function calls, then make it local.
If you need a variable to be persisted across function calls, but only for a particular class instance, make it an instance variable.
If you need something that is shared between every instance of that class, then use a class variable, however the need for this should be somewhat rare, so think carefully whether you really need a class variable.
(Disclaimer: I've only played with Ruby for about 2 weeks, so this is mostly a language agnostic answer, but I'm pretty sure it applies to Ruby. Feel free to correct me if I'm wrong.)

Resources