I know in Ruby, unlike other languages, there is class method inheritance.
class A
def self.filter_by
"filter"
end
end
class B < A
end
B.filter_by => "filter"
B's singleton class inherits methods from A's singleton class, and it appears to inherit the instance variables of those class methods:
class A
class << self
attr_accessor :filter_by
end
end
class B < A
end
B.filter_by = "filter"
A.filter_by = "no_filter"
=> "no_filter"
B.filter_by # B's value is not changed
=> "filter"
But it does not inherit the values:
class A
class << self
attr_accessor :filter_by
end
end
class B < A
end
A.filter_by = "filter"
B.filter_by => nil
B did not inherit the value set to A's #filter_by instance variable.
It can be resolved this way using the built-in inherited hook, or in Rails you can use class_attribute:
class A < ActiveRecord::Base
class_attribute :filter_by
end
class B < A
end
A.filter_by = "filter"
=> "filter"
B.filter_by # Now B's #filter_by inherited the value set in A's #filter_by
=> "filter"
But what I don't understand is why I cannot do this:
class A < ActiveRecord::Base
class_attribute :filter_by
end
class B < A
filter_by= %w(user_id_is status_id_is task_category_id_is)
end
B.filter_by => nil
Why does it return nil in this situation and not in the other situation? In this case as well, I set a value on self when I declared B (which is the singleton object).
Keep in mind that in your last example, there is
A class attribute named filter_by(with corresponding filter_by and filter_by= methods (inherited from class A)
A locally scoped variable inside of class B called filter_by.
The variable filter_by is not available outside of the scoping, and is also not available inside class B methods of any kind. In class B, you've set this variable to the list of strings, but when you do B.filter_by, the method inherited from class A is called. This value has not yet been set, so nil is returned.
Had you done self.filter_by = %w(...) inside the class, then it would have accessed the method filter_by= and set the class attribute as you intended.
You just forgot self. when you assigned to the attribute. Ruby thought you were assigning to a local. Fix it with self.:
class A < ActiveRecord::Base
class_attribute :filter_by
end
class B < A
self.filter_by= %w(user_id_is status_id_is task_category_id_is)
end
B.filter_by => nil
=> ["user_id_is", "status_id_is", "task_category_id_is"]
Related
I have a model Customer that accepts a virtual attribute opening_balance. Code looks something like this:
model Customer < ApplicationRecord
attr_accessor :opening_balance
has_one :ledger_account
after_create :open_ledger_account
def opening_balance
ledger_account.opening_balance if ledger_account.present?
end
private
def open_ledger_account
create_ledger_account!(opening_balance: self.opening_balance)
end
But the issue here is self.opening_balance is calling the method defined in the class not the value stored in attr_accessor's opening_balance attribute.
I tried one more solution:
def open_ledger_account
create_ledger_account!(opening_balance: self.read_attribute(:opening_balance))
end
But this also doesn't work.
How to read the value stored in the actual attribute? Any help would be appreciated. I am using rails 5.1.
Thanks!
attr_accessor defines a instance variable and a method to access it (read/write).
So the easy way is to write:
def open_ledger_account
create_ledger_account!(opening_balance: #opening_balance)
end
The read_attribute would only work if opening_balance was an attribute in the database on Customer.
First you have to understand that attr_accessor does not define instance variables. It just creates setter and getter methods. What attr_accessor :name does is:
class Person
def name
#name
end
def name=(value)
#name = value
end
end
Now you can access the instance variable from the outside:
p = Person.new
p.name = 'Jane'
puts p.name
And you can also access the instance variable from the inside by using the getter method instead of #name:
class Person
attr_accessor :name
def hello
"hello my name is: #{name}"
end
end
attr_accessor does not "define" a instance variable. There is no declaration of members/attributes in Ruby like in for example Java. An instance variables is declared when it is first set. Accessing an instance variable that has not been assigned a value returns nil.
So whats happing here:
class Customer < ApplicationRecord
attr_accessor :opening_balance
# ...
def opening_balance
ledger_account.opening_balance if ledger_account.present?
end
end
Is that you are overwriting the getter created by attr_accessor. If you want to access the instance variable itself just use #opening_balance.
However...
You should just use delegate instead:
class Customer < ApplicationRecord
has_one :ledger_account
delegate :opening_balance, to: :ledger_account
end
I have a code of the type
Module Themes
class NewTheme < Themes::Base
def1
end
def2
end
end
end
when i try to create an instance of new theme with Themes::NewTheme.instance the instance is empty, what is wrong here? Also what is ::Base? I can't find a defincition anywhere
You should be saying NewTheme.new instead of Themes::NewTheme to instanciate your class.
As for a base class, it's just a sort of 'final' parent class - a class that has no parent class, but has children classes (perhaps just a few, or many). Here's an example from ActiveRecord::Base
class Song < ActiveRecord::Base
# Uses an integer of seconds to hold the length of the song
def length=(minutes)
write_attribute(:length, minutes.to_i * 60)
end
def length
read_attribute(:length) / 60
end
end
In this case, the class Song is getting all of the methods defined in ActiveRecord::Base 'for free'
Like the following code shows, having class accessors defined in the superclass could have unexpected behaviours because the class accessor is the same variable for all the subclasses.
class Super
cattr_accessor :name
end
class SubA < Super; end
class SubB < Super; end
SubA.name = "A"
SubB.name = "B"
SubA.name
=> "B" # unexpected!
I want to have an independent class accessor for each subclass, so a possible solution is moving the cattr_accessor from the superclass and putting it in every subclass.
class Super; end
class SubA < Super
cattr_accessor :name
end
class SubB < Super
cattr_accessor :name
end
SubA.name = "A"
SubB.name = "B"
SubA.name
=> "A" # expected!
It is this solution a good practice? Do you know better alternatives?
Open up Super's singleton class and give that a regular attr_accessor:
class Super
class << self
attr_accessor :name
end
end
That should give you the semantics you want: a "class level instance variable".
However I'll note that any value set for :name on Super will not be inherited by Super's children. This makes sense if you think about it: the children inherit the attr_accessor, not the attribute itself.
There are some ways around this, most notably rails provides class_attribute which provides the ability of children to inherit the value of the parent's attribute unless explicitly overridden.
This question is related to extending class methods in Ruby, perhaps more specifically in the way that permalink_fu does so.
It appears that has_permalink on a model will not be available in a derived model. Certainly I would expect anything defined in a class to be inherited by its derived classes.
class MyScope::MyClass < ActiveRecord::Base
unloadable
self.abstract_class = true
has_permalink :name
end
class MyClass < MyScope::MyClass
unloadable
#has_permalink :name # This seems to be required
end
Is there something in the way permalink_fu mixes itself in that causes this issue?
I'm using the permalink-v.1.0.0 gem http://github.com/goncalossilva/permalink_fu
After investigating this, I can now see that the problem is related to how permalink_fu verifies it it should create a permalink or not. It verifies this by checking if the permalink_field of the class is blank or not.
What's the permalink_field? When you do
class Parent < ActiveRecord::Base
has_permalink :name
end
class Child < Parent
end
you can access the permalink by writing Parent.new.permalink or Child.new.permalink. This method name can be changed by writing
class Parent < ActiveRecord::Base
has_permalink :name 'custom_permalink_name'
end
If so, the permalink will be accessible by writing Parent.new.custom_permalink_name (or Child.new.custom_permalink_name).
What's the problem with this? The permalink_field accessor methods are defined on Parent's metaclass:
class << self
attr_accessor :permalink_field
end
When you run the has_permalink method, it calls Parent.permalink_field = 'permalink'.
The problem is that although the permalink_field method is available on all subclasses, its value is stored on the class it was called. This means that the value is not propagated to the subclasses.
So, as the permalink_field is stored on the Parent class, the Child does not inherit the value, although it inherits the accessor methods. As Child.permalink_field is blank, the should_create_permalink? returns false, and Child.create :name => 'something' does not create a permalink.
A possible solution would be to replace the attr_acessors on the metaclass with cattr_accessors on the class (lines 57 to 61 on the permalink_fu.rb file).
Replace
class << base
attr_accessor :permalink_options
attr_accessor :permalink_attributes
attr_accessor :permalink_field
end
with
base.cattr_accessor :permalink_options
base.cattr_accessor :permalink_attributes
base.cattr_accessor :permalink_field
Note that this will invalidate any possible customization on the subclass. You will no longer be able to specify different options for the subclasses, as these three attributes are shared by Parent and all its subclasses (and subsubclasses).
I have four tables: jp_properties, cn_properties, jp_dictionaries and cn_dictioanries.
And every jp_property belongs to a jp_dictionary with foreign key "dictionary_id" in table.
Similarly, every cn_property belongs to a cn_dictionary with foreign key "dictionary_id" in table too.
Since there are a lot of same functions in both property model and both dictionary model, I'd like to group all these functions in abstract_class.
The Models are like this:
class Property < AR::Base
self.abstract_class = true
belongs_to :dictionry,
:foreign_key=>'dictionary_id',
:class=> ---ModelDomainName--- + "Dictionary"
### functions shared by JpProperty and CnProperty
end
class Dictionary < AR::Base
self.abstract_class = true
has_many :properties,
:foreign_key=>'dictionary_id',
:class=> ---ModelDomainName--- + "Dictionary"
### functions shared by JpDictionary and CnDictionary
end
class JpProperty < Property
:set_table_name :jp_properties
end
class CnProperty < Property
:set_table_name :cn_properties
end
class JpDictionary < Dictionary
:set_table_name :jp_dictionaries
end
class CnDictionary < Dictionary
:set_table_name :cn_dictionaries
end
As you can see from the above code, the ---ModelDomainName--- part is either 'Jp' or 'Cn'.
And I want to get these string dynamically from the instances of JpProperty, JpDictionary, CnProperty or CnDictionary.
For example:
tempJpProperty = JpProperty.first
tempJpProperty.dictionary #=> will get 'Jp' from tempJpProperty's class name and then apply it to the "belongs_to" declaration.
So the problem is I don't know how to specify the ---ModelDomainName--- part.
More specifically, I have no idea how to get subclass's instance object's class name within the parent class's body.
Can you please help me with this problem?
Edit:
Anybody any ideas?
Basically, what I want to ask is how to 'delay the creation of the association until
the subclass is created.'?
You should be able to do something like this:
class Property < AR::Base
self.abstract_class = true
belongs_to :dictionry,
:foreign_key=>'dictionary_id',
:class=> #{singlenton_class} + "Dictionary"
def singlenton_class
class << self; self; end
end
end