can't understand what Base class is in rails - ruby-on-rails

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'

Related

ApplicationRecord: accessing model parent inside scope

I'm having some trouble understanding scope and when certain variables are accessible within Rails models. I am trying to access the parent of an EventInstance in order to determine whether it occurs in a certain time range.
class EventInstance < ApplicationRecord
belongs_to :event
# Event starts between 12am and 10am
scope :morning, -> { where(start_time: (event.start_time.midnight...event.start_time.change(hour: 10)) ) }
def event_name
# This works
event.name
end
end
Excuse my ignorance as I'm not quite up to speed on the magic of Rails. Why can I access event inside event_name, but not inside the scope? Is there any way to do this?
According to the docs, defining a scope is "exactly the same as defining a class method". You could accomplish the same thing by doing:
class EventInstance < ApplicationRecord
belongs_to :event
# Event starts between 12am and 10am
def self.morning
where(start_time: (event.start_time.midnight...event.start_time.change(hour: 10)) )
end
def event_name
# This works
event.name
end
end
Or even:
class EventInstance < ApplicationRecord
belongs_to :event
class << self
# Event starts between 12am and 10am
def morning
where(start_time: (event.start_time.midnight...event.start_time.change(hour: 10)) )
end
end
def event_name
# This works
event.name
end
end
In all those cases, you can't call the method on an instance of EventInstance because, well, it's an instance and not a class.
I imagine you could do something like:
class EventInstance < ApplicationRecord
belongs_to :event
delegate :start_time, to: :event
# Event starts between 12am and 10am
def in_morning?
start_time.in?(start_time.midnight...start_time.change(hour: 10))
end
def event_name
# This works
event.name
end
end
To determine if an instance of EventInstance occurs between 12am and 10am.
I will also note that Jörg W Mittag wishes to say:
I am one of those Ruby Purists who likes to point out that there is no such thing as a class method in Ruby. I am perfectly fine, though, with using the term class method colloquially, as long as it is fully understood by all parties that it is a colloquial usage. In other words, if you know that there is no such thing as a class method and that the term "class method" is just short for "instance method of the singleton class of an object that is an instance of Class", then there is no problem. But otherwise, I have only seen it obstruct understanding.
Let it be fully understood by all parties that the term class method is used above in its colloquial sense.

Rails STI class auto initialize

I'm trying to make a STI Base model which changes automatically to inherited class like that:
#models/source/base.rb
class Source::Base < ActiveRecord::Base
after_initialize :detect_type
private
def detect_type
if (/(rss)$/ ~= self.url)
self.type = 'Source::RSS'
end
end
end
#models/source/rss.rb
class Source::RSS < Source::Base
def get_content
puts 'Got content from RSS'
end
end
And i want such behavior:
s = Source::Base.new(:url => 'http://stackoverflow.com/rss')
s.get_content #=> Got content from RSS
s2 = Source::Base.first # url is also ending rss
s2.get_content #=> Got content from RSS
There are (at least) three ways to do this:
1. Use a Factory method
#Alejandro Babio's answer is a good example of this pattern. It has very few downsides, but you have to remember to always use the factory method. This can be challenging if third-party code is creating your objects.
2. Override Source::Base.new
Ruby (for all its sins) will let you override new.
class Source::Base < ActiveRecord::Base
def self.new(attributes)
base = super
return base if base.type == base.real_type
base.becomes(base.real_type)
end
def real_type
# type detection logic
end
end
This is "magic", with all of the super cool and super confusing baggage that can bring.
3. Wrap becomes in a conversion method
class Source::Base < ActiveRecord::Base
def become_real_type
return self if self.type == self.real_type
becomes(real_type)
end
def real_type
# type detection logic
end
end
thing = Source::Base.new(params).become_real_type
This is very similar to the factory method, but it lets you do the conversion after object creation, which can be helpful if something else is creating the object.
Another option would be to use a polymorphic association, your classes could look like this:
class Source < ActiveRecord::Base
belongs_to :content, polymorphic: true
end
class RSS < ActiveRecord::Base
has_one :source, as: :content
validates :source, :url, presence: true
end
When creating an instance you'd create the the source, then create and assign a concrete content instance, thus:
s = Source.create
s.content = RSS.create url: exmaple.com
You'd probably want to accepts_nested_attributes_for to keep things simpler.
Your detect_type logic would sit either in a controller, or a service object. It could return the correct class for the content, e.g. return RSS if /(rss)$/ ~= self.url.
With this approach you could ask for Source.all includes: :content, and when you load the content for each Source instance, Rails' polymorphism will instanciate it to the correct type.
If I were you I would add a class method that returns the right instance.
class Source::Base < ActiveRecord::Base
def self.new_by_url(params)
type = if (/(rss)$/ ~= params[:url])
'Source::RSS'
end
raise 'invalid type' unless type
type.constantize.new(params)
end
end
Then you will get the behavior needed:
s = Source::Base.new_by_url(:url => 'http://stackoverflow.com/rss')
s.get_content #=> Got content from RSS
And s will be an instance of Source::RSS.
Note: after read your comment about becomes: its code uses klass.new. And new is a class method. After initialize, your object is done and it is a Source::Base, and there are no way to change it.

Save inherited object to separate collection in Mongoid

I read up to inheritance in mongoid and it seems that all inherited classes will save in the base class, e.g.
class BaseClass
end
class ChildClass1 < BaseClass
end
class ChildClass2 < BaseClass
end
It seems that all these store in the BaseClass collection.
I actually want them to store in separate collections, e.g. ChildClass1 - collection and ChildClass2 - collection.
I realise this was posted a year ago, but this might be what you were looking for:
class BaseClass
include Mongoid::Document
def self.inherited(subclass)
super
subclass.store_in subclass.to_s.tableize
end
end
class ChildClass1 < BaseClass
end
class ChildClass2 < BaseClass
end
Please try this approach:
module Base
extend ActiveSupport::Concern
include Mongoid::Document
include Mongoid::Timestamps
included do
# Common code goes here.
end
end
class ChildClass1
include Base
end
class ChildClass2
include Base
end
I do this in my Rails 5 app and it works for sure.
I encountered the same problem and did not find a good solution on the web.
After few attempts, I developed a solution:
class A
include Mongoid::Document
include Mongoid::Timestamps
...
end
class B < A
def self.collection_name
:your_collection_name_1
end
...
end
class C < A
def self.collection_name
:your_collection_name_2
end
...
end
Before any access to mongo collection, mongoid gets the collection name from 'collection_name' method.
This way, I override the method 'collection_name' of mongoid class, which returns the collection name (instead of the name of the base class with 's': 'As')
So, all the write (or read) commands from class B will insert (select) into 'your_collection_name_1' collection and class C will insert into 'your_collection_name_2' collection.
It's impossible to do that. Because it's the concept of the STI In Mongoid like explain by Durran the Mongoid creator
If you really want save in several collection you need use Module include like :
class BaseClass
include MyModule
end
class ChildClass1
include MyModule
end
class ChildClass2
include MyModule
end

Subclassing ActiveRecord with permalink_fu in a rails engine

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

how to delay the creation of the association until the subclass is created in active record?

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

Resources