Setting attribute from model without using self doesn't work - ruby-on-rails

Device model has following attributes: name, version and full_name
Full name is name + version:
class Device < ActiveRecord::Base
def prepare
full_name = (!show_version || version.nil?)? name : name + " " + version.to_s
end
end
When i do following:
d = Device.new :name => "iPhone", :version => "4"
d.prepare
d.full_name # get nil
I get nil for "full_name" attribute
When I'm using "self", it works:
class Device < ActiveRecord::Base
def prepare
self.full_name = (!show_version || version.nil?)? name : name + " " + version.to_s
end
end
Doing "prepare" i get "iPhone 4" for "full_name" attribute.
Some people here told me, that it's a good manner to avoid using "self" inside class methods.
But this brings trouble.
The question is - why it does't works without using "self" ?

In these cases you have to use self, I don't think it's a trouble. If you are using self then the interpreter will know that you refer to the object's attribute. If you don't use self it means that it's just an local variable which is not stored anywhere after the method finishes.That's the normal behavior. You can also use self[:full_name]= ... as a setter method, but in this case doesn't really matter.
Update
#AntonAL
Because the getter methods are recognized without the self..
When you try to use the name attribute, the interpreter will look for a local variable within the current method. If doesn't finds then looks for an instance attribute. Example:
def your_method
self.name = 'iPhone'
puts name
#iPhone
name = 'iPod'
puts name
#iPod
puts self.name
#iPhone
end
And iPhone will be stored in your object instance's name attribute. And iPod will be lost after the method finishes.

When you use setters, you need to use self because otherwise Ruby will interpret it as a new local variable full_name being defined.
For getters, we dont need to call self because Ruby first search for a local variable full_name and when it does not have a local variable full_name, it will search for a method full_name and will get the getter. If you have a local variable defined full_name, it will return the value of local variable.
This is explained better in a previous question

This is expected because it's how ActiveRecord works by design. If you need to set arbitrary attributes, then you have to use a different kind of objects.
For example, Ruby provides a library called
OpenStruct
that allows you to create objects where you can assign arbitrary key and values. If you want get value from attribute without self, it's will give you instance of class OpenStruct with instance global variable attribute. But if you want set new attribute, it`s create new instance of class OpenStruct with new attributes. Without self you will have are old OpenStruct object which don't have attribute and will gave you error.
You may want to use such library and then convert the object into a corresponding ActiveRecord instance only when you need to save to the database.
Don't try to model ActiveRecord to behave as you just described because it was simply not designed to behave in that way

Related

Rails ActiveAttr Gem, manipulation of attributes within Class?

I have a Rails 5 class which includes ActiveAttr::Model, ActiveAttr:MassAssignment and ActiveAttr::AttributeDefaults.
It defines a couple of attributes using the method attribute and has some instance methods. I have some trouble manipulating the defined attributes. My problem is how to set an attribute value within the initializer. Some code:
class CompanyPresenter
include ActiveAttr::Model
include ActiveAttr::MassAssignment
include ActiveAttr::AttributeDefaults
attribute :identifier
# ...
attribute :street_address
attribute :postal_code
attribute :city
attribute :country
# ...
attribute :logo
attribute :schema_org_identifier
attribute :productontology
attribute :website
def initialize(attributes = nil, options = {})
super
fetch_po_field
end
def fetch_po_field
productontology = g_i_f_n('ontology') if identifier
end
def uri
#uri ||= URI.parse(website)
end
# ...
end
As I have written it, the method fetch_po_field does not work, it thinks that productontology is a local variable (g_i_f_n(...) is defined farther down, it works and its return value is correct). The only way I have found to set this variable is to write self.productontology instead. Moreover, the instance variable #uri is not defined as an attribute, instead it is written down only in this place and visible from outside.
Probably I have simply forgotten the basics of Ruby and Rails, I've done this for so long with ActiveRecord and ActiveModel. Can anybody explain why I need to write self.productontology, using #productontology doesn't work, and why my predecessor who wrote the original code mixed the # notation in #uri with the attribute-declaration style? I suppose he must have had some reason to do it like this.
I am also happy with any pointers to documentation. I haven't been able to find docs for ActiveAttr showing manipulation of instance variables in methods of an ActiveAttr class.
Thank you :-)
To start you most likely don't need the ActiveAttr gem as it really just replicates APIs that are already available in Rails 5.
See https://api.rubyonrails.org/classes/ActiveModel.html.
As I have written it, the method fetch_po_field does not work, it thinks that productontology is a local variable.
This is really just a Ruby thing and has nothing to do with the Rails Attributes API or the ActiveAttr gem.
When using assignment you must explicitly set the recipient unless you want to set a local variable. This line:
self.productontology = g_i_f_n('ontology') if identifier
Is actually calling the setter method productontology= on self using the rval as the argument.
Can anybody explain why I need to write self.productontology, using
#productontology doesn't work
Consider this plain old ruby example:
class Thing
def initialize(**attrs)
#storage = attrs
end
def foo
#storage[:foo]
end
def foo=(value)
#storage[:foo] = value
end
end
irb(main):020:0> Thing.new(foo: "bar").foo
=> "bar"
irb(main):021:0> Thing.new(foo: "bar").instance_variable_get("#foo")
=> nil
This looks quite a bit different then the standard accessors you create with attr_accessor. Instead of storing the "attributes" in one instance variable per attribute we use a hash as the internal storage and create accessors to expose the stored values.
The Rails attributes API does the exact same thing except its not just a simple hash and the accessors are defined with metaprogramming. Why? Because Ruby does not let you track changes to simple instance variables. If you set #foo = "bar" there is no way the model can track the changes to the attribute or do stuff like type casting.
When you use attribute :identifier you're writing both the setter and getter instance methods as well as some metadata about the attribute like its "type", defaults etc. which are stored in the class.

Setting an attribute in a rails concern [duplicate]

In testing a getter/setter pair in a rails model, I've found a good example of behavior I've always thought was odd and inconsistent.
In this example I'm dealing with class Folder < ActiveRecord::Base.
Folder belongs_to :parent, :class_name => 'Folder'
On the getter method, if I use:
def parent_name
parent.name
end
...or...
def parent_name
self.parent.name
end
...the result is exactly the same, I get the name of the parent folder. However, in the getter method if I use...
def parent_name=(name)
parent = self.class.find_by_name(name)
end
... parent becomes nil, but if I use...
def parent_name=(name)
self.parent = self.class.find_by_name(name)
end
...then then it works.
So, my question is, why do you need to declare self.method sometimes and why can you just use a local variable?
It seems the need for / use of self in ActiveRecord is inconsistent, and I'd like to understand this better so I don't feel like I'm always guessing whether I need to declare self or not. When should you / should you not use self in ActiveRecord models?
This is because attributes/associations are actually methods(getters/setters) and not local variables. When you state "parent = value" Ruby assumes you want to assign the value to the local variable parent.
Somewhere up the stack there's a setter method "def parent=" and to call that you must use "self.parent = " to tell ruby that you actually want to call a setter and not just set a local variable.
When it comes to getters Ruby looks to see if there's a local variable first and if can't find it then it tries to find a method with the same name which is why your getter method works without "self".
In other words it's not the fault of Rails, but it's how Ruby works inherently.

How self[:name] works rails

I created a reader method in my users model
def name
self[:name]
end
I'm having a hard time understanding self[:name]
it looks like I'm accessing a value with a key in a Hash but from what i can tell its not a Hash.
I have also tried to create classes in ruby to emulate this but cant get them to work so i"m not sure whether this is ruby or rails thing that I'm not understanding.
ActiveRecord supplies a [] method:
[](attr_name)
Returns the value of the attribute identified by attr_name after it has been typecast...
So saying self[:name] is just a round-about way to access the name attribute of your model.
[] is a method like any other in Ruby, you can define your own in any class you want:
class C
def [](k)
# do whatever you want
end
end
c = C.new
c[:pancakes]
ActiveRecord is used with data that is, more or less, a Hash backed by a relational database so saying model[:attribute_name] is fairly natural. Hence the existence of the [] method.

Attributes as local variables vs instance variables in Rails Validations

In custom validation methods, why are the attributes passed as local variables instead of being accessible as instance variables?
I was expecting to use #title instead of title in the custom validation below, but #title is nil in the code below. title contains the actual data.
attr_accessible :title
validate :do_check_title
def do_check_title
title =~ /^Alice in/ || errors.add(:title, "Not Alice in Wonderland")
end
Looking through the active_record/core.rb
def initialize(attributes = nil)
...
assign_attributes(attributes) if attributes
...
end
And then in active_record/attribute_assignment.rb
def _assign_attribute(k, v)
public_send("#{k}=", v)
So I guess, the attributes should be available as instance variables in the validation function.
Why are they nil?
It doesn't really matter if you access these from a validation or other instance methods. You can see what's happening from the console (pry):
u = User.first
show-method u.first_name=
This gives you something like this:
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
Now if you take a look at the write_attribute method, you can see that it deletes attribute cache etc. and then assign to attributes and it's just a hash.
u.attributes
So from now on, instead of the boring u.first_name = "foo", you can use this:
u.send :write_attribute, "first_name", "foo"
and it will do the same thing (3.2.10).
ActiveRecord stores the values in the attributes hash and not in instance variables. I generates accessor (getter/setter) methods on the fly while you access them.
Rails needs to support the livecycle of an entity, and wraps this code in the generated methods. This is needed, so that it can support ie. dirty? and changed? methods.
The next thing are associations, they are handled through proxies, so you need to call them with methods too.
Instance variables could be seen as volatile data. They are transparent to the persistance layer.

rails has_many and access inside of class

I have a rails model class
class Model < ActiveRecord::Base
has_many :object_collection
def add_object(object)
object_collection.push object // works
#object_collection.push object // does not work
self.object_collection.push object // works
end
end
I was wondering if someone can please explain to me why the # does not work yet self does i thought these two meant the same
cheers
They are not the same. Consider the following Ruby code:
class Person
attr_accessor :employer
end
john = Person.new
john.employer = "ACME"
john.employer # equals "ACME"
The method attr_accessor conveniently generates an attribute reader and writer for you (employer= and employer). You can use these methods to read and write an attribute, which is stored in the instance variable #employer.
Now, we can rewrite the above to the following, which is functionally identical to the code above:
class Person
def employer=(new_employer)
#works_for = new_employer
end
def employer
#works_for
end
end
john = Person.new
john.employer = "ACME"
john.employer # equals "ACME"
Now, the instance variable #employer is no longer used. We chose to write the accessors manually, and have the freedom to pick a different name for the instance variable. In this particular example, the name of the instance variable is different than the name of the attribute accessors. There is nothing that prevents you from doing that.
This is similar to how ActiveRecord stores its attributes internally. They are not stored in instance variables of the same name, that is why your push call to #object_collection does not work.
As you may understand, attribute readers and writers offer a certain abstraction that can hide the implementation details from you. Reading and writing instance variables directly in subclasses is therefore generally considered bad practice.
#foo identifies an instance variable called #foo. foo identified a method called foo.
By default, instance variables in Ruby are private. It means you cannot access the value of an instance variable unless you have some public method that exposes the value.
Those methods are called setters and getters. By convenction, setter and getter have the same name of the instance variable, but this is not a requirement.
class MyClass
def initialize
#foo
end
def foo=(value)
#foo = foo
end
def foo
#foo
end
def an_other_foo=(value)
#foo = foo
end
def an_other_foo
#foo
end
end
Though methods and instance variables can have similar names, thery are different elements.
If this topic is not clear to you, you probably need to stop playing with Rails and go back studying how Ruby works.
In your specific case, object_collection doesn't exist as an instance variable because it's an association method.
They do not mean the same thing. One is an instance variable, the other is a method.
The #foo means "the value of the instance variable foo", where as self.foo means "the value of a call to the method foo on myself".
It is typical for a method foo= to set the #foo instance variable, so I can see how someone new to the language might be confused. I'd encourage you to pick up a book on the ruby language. There's one specifically for people who have done some rails but never learned ruby proper. You often can hack rails without understanding the language or what these statements mean, but you'll be far less productive than someone who spends the small amount of time it takes to learn the ruby language itself.
As a general rule, use the self.foo form whenever you can, as this is less sensitive to changes in the classes definition.

Resources