module attr_accessor from another module - ruby-on-rails

In Ruby, what's the correct way for a 'child' module to inherit attributes from a 'parent' module? It seems that if you define an attribute in one module and then extend that module and mixin the child module to a class, I should be able to access the attribute from the child module, but not having any luck...
module A
attr_accessor :foo
end
module B
extend A
def not_worky
p "#{foo}"
end
end
class C
include B
end
class D
include A
end
irb(main):027:0* d = D.new
irb(main):028:0> d.foo=> nil
irb(main):033:0* c = C.new
irb(main):034:0> c.foo
NoMethodError: undefined method `foo' for #<C:0x553853eb>
irb(main):038:0> c.not_worky
NameError: undefined local variable or method `foo' for #<C:0x553853eb>

This was due to my own mis-understanding of what I was trying to do. Works as expected if I simply use the standard include mechanism. A more realistic example...
module App
attr_accessor :log
def initialize
self.log = 'meh'
end
end
module DB
include App
def go
p log
end
end
class Foo
include DB
end
irb(main):002:0> f = Foo.new
=> #<Foo:0x7cece08c #log="meh">
irb(main):003:0> f.go
"meh"

include is for adding instance methods and extends is for adding class methods. So you could do like this
B.foo #=> nil
read more here and here

Related

Ruby extend & include tracing code

I'm confused about using "include" vs "extend, after searching for hours all I got is that module methods used with instance of the class including the module, and module methods used with the class itself when the class extending the module of those methods.
but this didn't help me to figure out, why this code give error when commenting the extend module line in "#extend Inventoryable"
while work when uncomment it, here's the code
module Inventoryable
def create(attributes)
object = new(attributes)
instances.push(object)
return object
end
def instances
#instances ||= []
end
def stock_count
#stock_count ||= 0
end
def stock_count=(number)
#stock_count = number
end
def in_stock?
stock_count > 0
end
end
class Shirt
#extend Inventoryable
include Inventoryable
attr_accessor :attributes
def initialize(attributes)
#attributes = attributes
end
end
shirt1 = Shirt.create(name: "MTF", size: "L")
shirt2 = Shirt.create(name: "MTF", size: "M")
puts Shirt.instances.inspect
the output is
store2.rb:52:in `<main>': undefined method `create' for Shirt:Class (NoMethodError)
while when uncomment the "extend Inventoryable" to make the code work:
module Inventoryable
def create(attributes)
object = new(attributes)
instances.push(object)
return object
end
def instances
#instances ||= []
end
def stock_count
#stock_count ||= 0
end
def stock_count=(number)
#stock_count = number
end
def in_stock?
stock_count > 0
end
end
class Shirt
extend Inventoryable
include Inventoryable
attr_accessor :attributes
def initialize(attributes)
#attributes = attributes
end
end
shirt1 = Shirt.create(name: "MTF", size: "L")
shirt2 = Shirt.create(name: "MTF", size: "M")
puts Shirt.instances.inspect
makes the code work and output the following
[#<Shirt:0x0055792cb93890 #attributes={:name=>"MTF", :size=>"L"}>, #<Shirt:0x0055792cb937a0 #attributes={:name=>"MTF", :size=>"M"}>]
it's kinda confusing, but all I need to know, is why I need to extend the module in order to avoid the error ?, and how to edit this code to make it work without the extend method ? , what's left in the code that still depends on the extend ?
When you extend a module, the methods in that module become "class methods"**. So, when you extend Inventoryable, create becomes available as a method on the Shirt class.
When you include a module, the methods in that module become "instance methods"**. So, when you include Inventoryable, create is not available on the Shirt class (but is available on an instance of Shirt).
To make create available on the Shirt class when using include, you can use the included hook. That might look something like:
module Inventoryable
module ClassMethods
def create
puts "create!"
end
end
module InstanceMethods
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.include InstanceMethods
end
end
Then if you do:
class Shirt
include Invetoryable
end
You can do:
> Shirt.create
create!
=> nil
** The ruby purists in the crowd will correctly point out that, in ruby, everything is an instance method and that there are no class methods. That is formally 100% correct, but we'll use the colloquial meaning of class and instance methods here.
When you extend a module in a class, you get the module's methods exposed as class methods but if you include the module then you get the module's method as instance methods, in your example for you to be able to call create method of Inventoryable class you need to invoke it using an instance of Shirt class (if you include the module)
shirt1 = Shirt.new(attributes).create(attributes)
Without more info I can't tell what you are trying to do but you need to redesign the initialize and create methods to decide where or what to do in those methods.
I'll try to explain it using a simple example
module A
def test
puts "ok"
end
end
class B
include A
end
class C
extend A
end
puts C.test # here you invoke the method against the class itself
puts B.new.test #here you create an instance to do it
Hope it helps.
At the end of the day, it's really simple:
C.include(M) makes the current superclass of C the superclass of M and M the superclass of C. In other words, it inserts M into C's ancestry chain.
obj.extend(M) is (roughly) the same as obj.singleton_class.include(M).

Rails: when do we must put :: before some classes for rails understand base class

In Ruby, I understand that ::ClassName for calling class at base module. For example, here is my code:
module HHT
module V1
module OfflineCheckIn
class PutOfflineCheckInProductsApi < ApplicationApi
put 'offline' do
ActiveRecord::Base.transaction do
OfflineCheckIn.create(check_in_param) # exception here
end
end
end
end
end
end
When I run, I meet exception:
NoMethodError (undefined method `create' for
HHT::V1::OfflineCheckIn:Module)
As I understand, Rails understand that OfflineCheckIn currently inside module HHT::V1::OfflineCheckIn, so I must call at base class ::OfflineCheckIn. Thing that I don't understand is: at another controller, some previous programmer implements same way with me, but he doesn't need to call :: before model.
So my question is: when we don't need to use :: before class and rails can understand that is base class ?
Thanks
You have to call class as ::ClassName if in your hierarchy there's a class/module with the same name, to differentiate between them, for example:
class Foo; end
module Bar
class Foo; end # this is another Foo
def self.a
puts ::Foo == Foo
end
end
module FooBar
def self.a
puts ::Foo == Foo
end
end
Bar.a # => false
FooBar.a # => true
Here we have ::Foo and ::Bar::Foo, but shorthand Foo points to one of them depending on context.
Also it does not matter if the entities are classes or modules, both are just assigned as a value for a constant:
module Foo; end
module Bar
Foo = "a string"
def self.baz
puts Foo.class
end
end
puts Foo.class # => Module
Bar.baz # => String

Undefined class method using Rails Concerns

I did everything pretty much as described here: question
But I keep getting error:
NoMethodError: undefined method `parent_model' for Stream (call 'Stream.connection' to establish a connection):Class
In model/concerns faculty_block.rb
module FacultyBlock
extend ActiveSupport::Concern
included do
def find_faculty
resource = self
until resource.respond_to?(:faculty)
resource = resource.parent
end
resource.faculty
end
def parent
self.send(self.class.parent)
end
end
module ClassMethods
def parent_model(model)
##parent = model
end
end
end
[Program, Stream, Course, Department, Teacher].each do |model|
model.send(:include, FacultyBlock)
model.send(:extend, FacultyBlock::ClassMethods) # I added this just to try
end
In initializers:
require "faculty_block"
method call:
class Stream < ActiveRecord::Base
parent_model :program
end
It seems that the Stream is loaded before loading concern, make sure that you have applied the concerns inside the class definition. When rails loader matches class name for Stream constant, it autoloads it before the finishing evaliation of the faculty_block, so replace constants in it with symbols:
[:Program, :Stream, :Course, :Department, :Teacher].each do |sym|
model = sym.to_s.constantize
model.send(:include, FacultyBlock)
model.send(:extend, FacultyBlock::ClassMethods) # I added this just to try
end

How to make module variables that can be accessible in class and in each model instance?

I can access to module pluggins_list method through e.g. #page.plugins.pluggins_list, but if I'm trying to get Plugginable methods or variables in Plugin model by e.g. #page.page_elements.first.get_pluggin I got nil or undefined. How to make module pluggins accessable outside and also that they can be available in each Plugin instance?
Edit, better example:
[3] pry(#<Plugin>)> plg = Plugin.new(name: "nutella")
=> #<Plugin id: nil, element_type: "Plugin", name: "nutella", page_id: nil>
[4] pry(#<Plugin>)> plg.get_pluggin
[5] pry(#<Plugin>)> plg.pluggins
=> nil
[6] pry(#<Plugin>)> pluggin_by_name(name)
NoMethodError: undefined method `find' for nil:NilClass
Current code:
module Plugginable
extend ActiveSupport::Concern
attr_accessor :pluggins
included do
#pluggins = [{name: "dupa"},{name: "hello"}]
end
module ClassMethods
def pluggins_list
#pluggins
end
end
def pluggin_by_name(name)
#pluggins.find {|plg| plg.name === name.downcase }
end
module InstanceMethods
end
end
class Plugin < PageElement
include Plugginable
def get_pluggin
binding.pry
# pluggin_by_name(name)
end
end
You try to call pluggin_by_name, which is class singleton method, on the instance (your self is an instance of Plugin in pry console). This should work:
Plugin.pluggin_by_name(name)
Also, this part:
included do
#pluggins = [{name: "dupa"},{name: "hello"}]
end
makes no sense. You want your #pluggins, as far as I understand, as an instance variable of Plugin, but in this block self certainly isn't Plugin instance but, if I remember correctly, Plugin class. If you want pluggins attr reader to work, maybe you should set some kind of default value, like this:
module Plugginable
attr_writer :pluggins
def pluggins
#pluggins ||= [{name: 'dupa'}, {name: 'hello'}]
end
end

ruby super keyword

From what I understand, super keyword invokes a method with the same name as the current method in the superclass of the current class. Below in the autoload method, there is a call to super. I would like to know in which superclass I would find a method with the same name or what does the call to super do here
module ActiveSupport
module Autoload
...
def autoload(const_name, path = ##at_path)
full = [self.name, ##under_path, const_name.to_s, path].compact.join("::")
location = path || Inflector.underscore(full)
if ##eager_autoload
##autoloads[const_name] = location
end
super const_name, location
end
....
end
end
module ActiveRecord
extend ActiveSupport::Autoload
...
autoload :TestCase
autoload :TestFixtures, 'active_record/fixtures'
end
This code is from the rails master branch. Thanks much.
The example provided in the Ruby Docs for the super keyword:
module Vehicular
def move_forward(n)
#position += n
end
end
class Vehicle
include Vehicular # Adds Vehicular to the lookup path
end
class Car < Vehicle
def move_forward(n)
puts "Vrooom!"
super # Calls Vehicular#move_forward
end
end
Inspecting ancestors
puts Car.ancestors.inspect
# Output
# [Car, Vehicle, Vehicular, Object, Kernel, BasicObject]
Note the inclusion of the Vehicular Module object!
Check objRef.class.ancestors or ClassName.ancestors to know the inheritance chain. If the super class does not contain the method, then all modules included by the super class are checked (last included checked first). If no match, then it moves up one level to the grandparent class and so on.
You can use the list of ancestors and then call AncestorClass.methods.select{|m| m.include?("auto_load")} to zone in on the method that's being called.
(Note: the above code is Ruby 1.8. In 1.9 methods returns symbols instead of strings. so you'd have to do a m.to_s.include?(...)
Use Pry
Insert a binding.pry call right before you use super, and then invoke show-source -s (-s means superclass) to show the superclass method and find out where it's defined:
class A
def hello
puts "hi"
end
end
class B < A
def hello
binding.pry
super
end
end
b = B.new
b.hello
From: (pry) # line 7 B#hello:
7: def hello
=> 8: binding.pry
9: super
10: end
[1] (pry) #<B>: 0> show-source -s
From: (pry) # line 2:
Number of lines: 3
Owner: A # <--see owner here (i.e superclass)
Visibility: public
def hello
puts "hi"
end
[2] (pry) #<B>: 0>
The super keyword checks all the way up the ancestry tree to find the inherited method.
Do a search on the entire rails master branch. You will only find one def autoload which is exactly the one you're looking at in active_support/lib/active_support/dependencies/autoload.rb.
The method being overridden is native Ruby. It is Module#autoload
I added this method to find the owner of a method to my .irbrc, does anyone see a better way to do this, especially in handling singleton methods where the superclass of the singleton class is the singleton class of the superclass?
class Object
def find_method(method_string)
if klasses = self.class.ancestors.select { |a| a if a.methods.include? method_string }
puts "class method in #{klasses.join(',')}" unless klasses.empty?
end
if klasses = self.class.ancestors.select { |a| a if a.instance_methods.include? method_string }
puts "instance method in #{klasses.join(',')}" unless klasses.empty?
end
rescue
raise "owning class not found"
end
end
The relevant superclass method is probably Module#autoload.

Resources