I'm requesting a high-level overview of how to structure the directory of a project using the Template Design Pattern in Ruby.
I have a project directory with one venue.rb file and one folder containing base.rb. I see them used a lot in my companies Ruby on Rails project but would like to understand how to structure the project regarding the template design pattern independent of Rails.
Current Setup:
~./project
+--venue.rb
+--venue
|
+--base.rb
venue.rb
require_relative 'venue/base'
class Venue
end
puts "Loaded class Venue:"
a = Venue.new
a.base
~./project/venue/base.rb
module Base
class Venue
def base
puts "base"
end
end
end
puts "Loaded module Base:"
When I run the venue.rb I get:
#=> ruby venue.rb
Loaded module Base:
Loaded class Venue:
venue.rb:9:in `<main>': undefined method `base' for #<Venue:0x007fe77209cf20>
(NoMethodError)
I'm pretty sure I've misunderstood inheritance but I believe this should work.
The nice thing about require (and require_relative) in Ruby is that it's almost equivalent with just substituting the text of the script for the require.
So your venue.rb is pretty much
module Base
class Venue
def base
puts "base"
end
end
end
puts "Loaded module Base:"
class Venue
end
puts "Loaded class Venue:"
a = Venue.new
a.base
This declares two different classes called Venue that are in different namespaces: ::Venue and Base::Venue. When you do a = Venue.new you are referring to the ::Venue one that has no base method (or any methods, for that matter).
If you want your Base module to modify the top-level Venue, you need to specifically refer to it:
module Base
class ::Venue
def base
puts "base"
end
end
end
You're missing the module namespace in venue.rb by adding it you can fix the undefined class and also remove the class definition stub.
require_relative 'base'
puts "Loaded class Venue:"
a = Base.Venue.new
a.base
Related
I know how a module can be used in a class in Ruby:
module Calculator
def add(a,b)
a+b
end
end
class Watch
include Calculator
def time
Time.now
end
end
w = Watch.new()
puts w.time # 2020-03-11 22:34:01 +0000
puts w.add(3,5) # 8
But, sometimes I can see some module has a class inside. For instance, in Rails, helpers:
module MyModule
class MyClass
def foo
puts 'foo'
end
end
end
What's the point of this?
Why would I have a class inside a module?
Modules can be used that way for scoping purposes. In your example you can refer to MyClass from outside the module by MyModule::MyClass.
There are several uses for this, like grouping together related classes under a common namespace.
It's better to get more information about this from some Ruby tutorials (for examples and stuff). Check out "Ruby - Modules and Mixins" for more information.
I'm trying to learn how to program with Ruby and I want to create separate files for separate classes, but when I do I get the following message:
NameError: uninitialized constant Book
const_missing at org/jruby/RubyModule.java:2677
(root) at /Users/Friso/Documents/Projects/RubyApplication1/lib/main.rb:1
However, it works if I put the class directly into the main file. How can I solve this?
Main code:
book1 = Book.new("1234", "Hello", "Ruby")
book2 = Book.new("4321", "World", "Rails")
book1.to_string
book2.to_string
Class code:
class Book
def initialize(isbn,title,author)
#book_isbn=isbn
#book_title=title
#book_author=author
end
def to_string
puts "Title: ##book_title"
puts "Author: ##book_author"
puts "ISBN: ##book_isbn"
end
end
In order to include classes, modules, etc into other files you have to use require_relative or require (require_relative is more Rubyish.) For example this module:
module Format
def green(input)
puts"\e[32m#{input}[0m\e"
end
end
Now I have this file:
require_relative "format" #<= require the file
include Format #<= include the module
def example
green("this will be green") #<= call the formatting
end
The same concept goes for classes:
class Example
attr_accessor :input
def initialize(input)
#input = input
end
def prompt
print "#{#input}: "
gets.chomp
end
end
example = Example.new(ARGV[0])
And now I have the main file:
require_relative "class_example"
example.prompt
In order to call any class, or module from another file, you have to require it.
I hope this helps, and answers your question.
You need to instruct the Ruby runtime to load the file that contains your Book class. You can do this with require or require_relative.
The latter is better in this case, because it loads the file relative to the directory in which the file containing the require is specified. Since that's probably the same directory, you can just require_relative the file name, without the .rb extension by convention.
You can google 'require vs require_relative ruby' to find out more about the differences.
I have a module with a define method that creates a class dynamically like this:
require "active_support/all"
class SomeBaseClass
# code
end
module MyModule
def self.define(_class_name)
class_name = _class_name.classify
Object.const_set(class_name, Class.new(SomeBaseClass))
end
end
Then, if I do: MyModule.define(:my_class) I will get: MyClass
My spec:
describe "#define" do
it "creates a dynamic class" do
MyModule.define("my_class")
expect(subject.const_defined?("MyClass")).to be_truthy
end
end
This works beautifully... But! when I create a new spec defining MyClass I get this warning: warning: already initialized constant MyClass
This is happening because I've been created MyClass in the previous spec. So, the question is: How can I avoid this? I want "a fresh start" on each spec.
UPDATE: solution based on #giglemad answer...
before do
Object.send(:remove_const, :MyClass) if Object.const_defined?("MyClass")
end
describe "#define" do
it "creates a dynamic class" do
MyModule.define("my_class")
expect(subject.const_defined?("MyClass")).to be_truthy
end
end
You could define a different class name every time based on the name + some convention. I use timestamps to do so.
def nano_timestamp_string
Time.now.to_f.to_s.sub('.','')
end
You can pretty much reuse that convention any time you need something unique but still want to write your tests the same way. I use it for unique emails for instance. If you still want to def and then undef a constant with the same name then you might want to use
Object.send(:remove_const,:Myconst)
Apologies for the title, suggestions to make it clearer are welcome.
I have created a module (we'll denote this by M) which, when included inside a class, will cause it to obtain new class methods and instance methods (Apologies if my terminology is incorrect). This is achieved by = class F including the A::ModuleInstanceMethods module in the code below.
Now that I've done that, I am trying to create a new module (we'll call this new module M') which includes the module M, such that when M' is included in a class, the class should gain the appropriate class and instance methods as per class F. This is where I get stuck. Examples of such a class is G in the code below.
I'd also like classes which include module M'' (module M'' will includes M') to have the same functionality. An example will be class H in the code below. The same should go for classes which include M''' (which itself includes M''), classes which M'''' (which itself includes M'''), and so on. It's pretty similar to an inheritance hierarchy.
If my textual explanation is confusing, do read the code below. In particular, I'd like to resolve the failures caused by calling G.class_method_one and H.class_method_one, but I lack the knowledge to do so.
I know it is possible to just extend the A::ModuleClassMethods module in the classes that I'm interested in, but I wish to avoid doing this. The same could also be achieved by manually adding the portion of the self.included function in A::ModuleInstanceMethods with the base.instance_of? Class, but if possible I'd like to do it programatically instead of copying and pasting the same code in many different sites.
module A
module ModuleClassMethods
def class_method_one
2
end
end
module ModuleInstanceMethods
def instance_method_one
3
end
def self.included(base)
if base.instance_of? Class
base.extend(A::ModuleClassMethods)
elsif base.instance_of? Module
# Intended functionality:
# When modules that `include` A::ModuleInstanceMethods are themselves
# included in a class (such as module `A::D` included in class `F`),
# class `F` will get the functions defined in the A::ModuleClassMethods
# module as class level methods
end
end
end
module D
include A::ModuleInstanceMethods
end
module E
include D
end
end
class F
include A::ModuleInstanceMethods
end
class G
include A::D
end
class H
include A::E
end
F.class_method_one # 2
F.new.instance_method_one # 3
G.new.instance_method_one # 3
# below statement fails
# G.class_method_one
H.new.instance_method_one # 3
# below statement fails
# H.class_method_one
Thank you.
I've seem to have figured it out. This solution makes use of module_eval. For modules, it adds a self.included function which calls A::ModuleInstanceMethods.included. I wouldn't mind learning about more elegant solutions.
module A
module ModuleClassMethods
def class_method_one
2
end
end
module ModuleInstanceMethods
def instance_method_one
3
end
def self.included(base)
if base.instance_of? Class
base.extend(A::ModuleClassMethods)
elsif base.instance_of? Module
base.module_eval {
def self.included(base)
A::ModuleInstanceMethods.included(base)
end
}
end
end
end
end
I have a class within several modules: This::Is::A::Long::ClassName. Is there any way, within one script or method, to make ClassName available without having to reference the namespace? Instead of writing:
This::Is::A::Long::ClassName.do_something
This::Is::A::Long::ClassName.do_something_else
This::Is::A::Long::ClassName.do_something_different
is anything as below possible?
include This::Is::A::Long
ClassName.do_something
ClassName.do_something_else
ClassName.do_something_different
If you are using modules for namespacing, the code you posted should work, see this example:
module Long
module Name
class ClassName
end
end
end
ClassName
# => ... uninitialized constant ClassName (NameError)
include Long::Name
ClassName
# => Long::Name::ClassName
Ruby has no equivalent to C++ using namespace, and you can not reference a class without being in the right namespace, but you can always make it a variable since a class is also an object
long_class = This::Is::A::Long::ClassName
long_class.do_something
long_class.do_something_else
# and so on
EDIT
An include does not put you in the right namespace, it includes the methods & classes in the module you are including (that is, it puts the module in the classes ancestors) and is therefore most certainly not suitable for your needs: Consider the following:
module This
module Is
module A
def foo
puts 'A#foo'
end
def bar
puts 'A#bar'
end
class ClassName
end
end
end
end
Now, you may not want to write This::Is::A::ClassName in another class, let's say:
class C
def foo
puts 'C#foo'
end
end
class B < C
include This::Is::A
end
Now, B.new.foo still puts out C#foo, right? Wrong. Since you included the module, the method has been overwritten.