I'm just learning rails and have noticed that when I create an object that inherits from ActiveRecord::Base (i.e. from a model I migrated), the instance variables in the object do not have a # symbol in front of them.
Is this a rails thing, or did I misunderstand something while learning ruby?
Thanks in advance for your help.
Rails doesn't use individual instance variables to store field data. Instead it makes certain methods available to you which set the correct variables. It helps Rails better populate models when using finds and allows other methods that improve how dynamic Rails is.
When accessing the "instance variables" of your object, you're actually interacting with the getter/setter methods defined by rails which in turn interact with the real instance variables.
This is actually very useful as it allows you to override them when required to modify the behaviour of the variables within your classes.
The columns of your model are not stricto sensu instance variables.
You have access to their getter/setter but they are by nature different: they are meant to be persisted.
Rails defines getter/setter for all model attributes.
Getter/setter can ben declared with attr_accessor function.
class Foo
attr_accessor :bar
def do_something
self.bar=2
#bar=2 # does the same as above
end
end
Related
I understand what these statements do, but not how to refer to them. They exist within a class, outside of that class's methods and perform a variety of functions.
Collectively, what are they called?
These methods are really just class methods. Try this:
class Test
def self.before_create
puts "before_create"
end
before_create
end
The specific use case you mentioned - Rails DSL methods such as before_create, that are only available inside a class body — are often called class macros. Rubys metaprogramming abilities give you multiple ways to build them. A simple one is to make them private:
module Foo
private
def before_create
puts "before_create"
end
end
class Bar
extend Foo
before_create
end
before_create is now accessible inside the class body, but not from outside:
Bar.before_create
NoMethodError: private method `before_create' called for Bar:Class
In pure Ruby terms, they are all just method calls.
However, they do have a common theme. In the way they are constructed and used, you could consider them part of a Domain-Specific Language (DSL) - the ones you list are part of Active Record's DSL for creating data models.
Ruby lends itself well to creating DSL-like mini languages, using mix-ins or a base class in order to provide a set of class methods, which in turn will store data or create methods on the class and instances of it using meta-programming techniques.
I am watching Code School Rails testing course. There is an instance of the class zombie. The zombie model has a method:
def avatar_url
...
end
Within the test, .rb file has the following:
z.avatar_url
When I call a method like this, how does Rails distinguish if I'm calling a controller or model method? I hadn't thought of calling a model method from other than a controller, and only like Model.method and not object.method.
If both my controller and my model have a method with the same name, how would Rails know which one to call?
Update:
Lets take the class String as example, it is not a model, right?
So I could say:
s = String.new
s.capitalize
If this call doesn't go to a model and not to a controller, where does it go then? Where would a class like String be defined in the Rails directory?
A method inside a controller can only be called via URL.
Example:
/things/super_action
Should call the def super_action inside ThingsController.
As for Model methods, they can be accessed anywhere. Just note if they are instance or class methods:
Model.ultra_method
This is a class method call, it is probably defined as def self.ultra_method.
m = Model.new
m.instance_method
This is a instance method call, and it is probably defined as def ultra_method.
UPDATE
String is a core class of ruby language. As is Array, Number, etc. In your example you are creating an instance of String and calling an instance method of the String class.
It seems like you're new to Ruby as well as Rails. In Ruby, a class is sort of like a description of a type of object (although the class itself is an object, too). Whenever there is a class defined, you can create new instances of it, as with String.new. Note that classes always have capitalized names.
Class methods are methods that work on the class itself. You can tell when a method is a class method because it will be attached to the capitalized name of the class (just like String.new). On the other hand, instance methods only work on an instance of the class, not on the class itself (eg str = String.new; str.capitalize!). Usually there are more instance methods than class methods, because instances are the things that you're actually working with (new is the most common class method you'll see).
As others have mentioned here, String is not a Rails model; it's a basic Ruby class. When you're working in Rails, you have access to all the regular Ruby classes as well as other classes and methods that are defined within Rails' source code. So String is not defined in Rails itself, but Rails does provide some useful instance methods for strings (eg str.to_date).
A model in Rails is really just a Ruby class. To understand the workings of a model, you should make sure you understand how Ruby classes work. What makes Rails models special is that they inherit from a class defined in Rails' source code known as ActiveRecord (any class in Ruby can inherit from another class, this is just one example of that). ActiveRecord has a number of class and instance methods, which are also available to your models because they inherit from ActiveRecord. For example, if you have a class (model) called Person, you can automatically use the Person.find(id) class method to look up a particular instance of the Person class in the database. You also have the person.save instance method to save the instance to the database.
All of this was confusing to me when I first started, so my best advice is to familiarize yourself with Ruby as you learn Rails.
You can call Model class/instance methods from anywhere in Rails. Model are just the mapping to your database and acts as a proxy for your database. When you are calling
z.avatar_url
you are calling a method on "z" model instance. So not matter from where you call it will always call the model's method.
If you define a method with same name in both controller and model, you would always be calling a model's method with model instance or model class. Controller methods are simply action in Rails they are never referred directly from anywhere. They are used for Rails routing.
Hope I am clear.
I know that objects in Rails that extend ActiveRecord::Base can use a constructor that takes a hash of attributes names to values, but I can't seem to find where that hash constructor is actually defined (doesn't seem to be directly in ActiveRecord::Base). Can anyone point me to where that constructor exists?
In rails 3.2.7 the initialize method appears to be in ActiveRecord::Base.
In the current master (commit c10202208982bfa02ffd16d1614992228391d781 at time of writing), Base includes ActiveRecord::Model, which in turn includes ActiveRecord::Core, where initialize is defined. It appears to be very similar to the method in 3.2.7.
I've read that ruby objects are just places where we can store instance variables (and class pointers). So:
class Person
def initialize(age)
#age = age
end
end
Now if we run:
p = Person.new(101)
Then we get:
#<Person:0x86cf5b8 #age=101>
Great, the property age is stored as an instance variable, as expected. But things work a little differently if we convert the model to inherit from ActiveRecord. Now, after instantiating an new Person, we see this:
# timestamps removed
#<Person id: 1, age: 101 >
The age property no longer appears to be an instance variable. So what is really going on here?
I know that we can access the #attributes instance variable, which contains a hash of all the properties and values, so I'm wondering if ActiveRecord is possibly modifying the console output to present the objects attributes in this way.
Is it possible to instantiate a Ruby object where properties are held as attributes and not instance variables, without using ActiveRecord?
Yes, you can extend a ruby class with include ActiveModel::AttributeMethods to expose your instance variables as ActiveModel-like attributes.
See the docs for more information.
as you see in your code 'storing' properties as instance vars was your own doing, so if you wanna hold them any other way, is also up to you. ruby gives you convenience class methods to define getter and setter methods like attr_accessor.
also worth noting, that if you inherit from ActiveRecord::Base, you should not override initialize.
I'm wondering if ActiveRecord is possibly modifying the console output to present the objects attributes in this way.
Well, kind of. The method responsible for that is inspect, and it's implemented by Object in a way that (emplasis mine):
...shows the object's class name, an encoding of the object id, and a list of the instance variables and their values (by calling inspect on each of them).
There's more right after:
User defined classes should override this method to provide a better representation of obj.
This is exactly what ActiveRecord does and the reason why you're seeing this output. The overridden method does not list instance variables, but displays AR attributes.
So just because you aren't seeing the instance variable in the console doesn't mean it isn't there!
I am getting confused as to how to properly set variables in a initializer, I want these to be class level variables, not instance.
And I also want to then create a single instance of another object (it is a connection object, which already has connection pooling built in, so I just need a single reference to it).
My initializer /initializers/my_class.rb
yml = YAML.load_file("#{Rails.root}/config/my_class.yml")
MYMODULE::MyClass.init(yml)
And here is my my_class.rb:
module MYMODULE
class MyClass
def self.init(yml)
#post_url = yml["defaults"]["post_url"]
end
def self.post_url
#post_url
end
# this should be a single instance
def connection_pool
# ???
end
end
end
These class level variables, how can I access them from both class methods and instance methods?
I'm getting wierd behaviour, and I'm confused as to how to reference the post_url from inside of either class methods and instance methods.
I have seen the following ways, unsure which is correct:
self.class.post_url
MyClass.post_url
#post_url
post_url
self.post_url
self.class.post_url or MyClass.post_url will work. The difference is how they work for subclasses (in the former case, subclasses will use their own version of this variable automatically, in the latter, they would share the variable in MyClass).
There is no way to directly access class instance variables from an instance: you have to call a class method which returns (or sets) them. See also: cattr_accessor.
That said, if this is really a singleton, it seems a little strange to me that you would configure part of it on the class, and then reference that info in the (single) instance. Wouldn't it make more sense just to configure this stuff on the instance? Or use a module as a singleton and not create an instance at all?