Why can I extend a Ruby class multiple times with require_relative? - ruby

Assume I have the following two files:
app.rb
class App
def a
"a"
end
require_relative 'b'
end
b.rb
class App
def b
"b"
end
end
App in app.rb is being successfully extended by App in b.rb, but I have no idea why. Why does this work?

Your code is equivalent to:
class App
def a
"a"
end
end
class App
def b
"b"
end
end
You might suppose it is equivalent to:
class App
def a
"a"
end
class App
def b
"b"
end
end
end
It isn't, because the require'd file code is always executed in a global scope.

require_relative is pulling the code from b.rb into app.rb
Ruby is a dynamic language and allows you to open and close existing classes at will. Basically when you run app.rb, it reads method 'a' and then reads b.rb giving you access to method 'b' from the app.rb program.
Require_relative assumes the required file is in app.rb's root directory.
This becomes very useful in more complex situations when you might want to add methods to previouskly existing (Ruby Core) classes like Array.
Read this book for a more in depth understanding:
Metaprogramming with Ruby

Related

How to split a Sinatra app with different scopes?

I'm splitting a large Sinatra file into different files using registers. This is one way to have a modular Sinatra app using extensions.
I'm ending up with something like the following code:
MyApp < Sinatra::Base
register OneRegister
register SecondRegister
end
module OneRegister
def self.registered(app)
app.helpers OneRegisterHelper
app.get "/one-endpoint" do
do_stuff
end
end
module OneRegisterHelper
def do_stuff
# Some code
end
end
end
module SecondRegister
def self.registered(app)
app.helpers SecondRegisterHelper
app.get "/second-endpoint" do
do_stuff
end
end
module SecondRegisterHelper
def do_stuff
# Different code
end
end
end
The problem is how Sinatra works with registers and helpers. Every time I create a new helper for a register I'm polluting the main Sinatra app scope with the methods in the helpers.
So, the method do_stuff is going to be overwritten by the SecondRegisterHelper (this is how Ruby works when including a module) but I'd like to have different implementations for the methods without worry if I'm using the same method name or a different one (image an app with 25 registers with small methods in each one).
Basically, I'd like to have different registers with private methods because I usually write very small private methods with a single responsibility. Any ideas, how I can achieve this?
I don't think this is achievable in the way you are trying. If you have methods with similar names in different modules mixed into a single class the last just wins.
So in this case I would create a modular app combined with a config.ru to setup your application.
class OneRegister < Sinatra::Base
# helpers here
end
class SecondRegister < Sinatra::Base
# helpers here
end
In config.ru
app = Rack::URLMap.new(
'/one-endpoint' => OneRegister,
'/second-endpoint' => TwoRegister
)
run app
No you helpers are scoped to a single class.

hot to write an api in a voltrb gem

I have created a local volt gem and I would like to write a simple api:
def add(a,b)
a+b
end
But I don't know where to write this code and how to require it from the app. The only place where I have got it working is in controllers/main_controller.rb of the new created gem:
module DtPicker
class MainController < Volt::ModelController
end
def self.add(a,b)
a+b
end
end
I think this is not the correct place but the lib folder. In this place I can't get it working. What am I doing wrong? Thanks.
I have to:
require 'volt/dt_picker'
where I want to use DtPicker.add.
The method is defined in the file lib/volt/dt_picker/dt_picker.rb:
module DtPicker
def self.add(a,b)
a+b
end
end

Ruby code order

Why can the following part # def games # #games = games # end come at the very end (bottom) of the code and still work? I thought Ruby reads the code from top to bottom. If I do not define games at the top, shouldn't it give an error?
class Library
# def games
# #games
# end
def initialize(games)
#games = games
end
def add_game(game)
games << game
end
# The following lines should come at the top of this code.
def games
#games
end
end
games = ['WoW','SC2','D3']
lib = Library.new(games)
lib.games #=> WoW,SC2,D3
lib.add_game('Titan')
lib.games #=> WoW,SC2,D3,Titan
When the method is defined, ruby is not running it. It's just available for the instance to use after you've invoked the class.
I generally put my methods in alphabetical order to make it easier to navigate my code as it grows. This is a personal preference.
Ruby allows you to structure and organize your classes/modules however is logical/beneficial to you.
To clarify, Ruby classes are executed when they're defined, but methods are not.
example.rb
class Example
puts "hello"
def my_method
puts "world"
end
end
Run it
$ ruby example.rb
hello
Because Ruby executes classes, that's how things like macros work in Ruby classes.
class Example2
attr_accessor :foo
end
attr_accessor is a method that gets called when the class is executed. In this case attr_acessor will setup get and set functions for the #foo instance variable.
If Ruby didn't execute your classes, this code would have to be called manually in some sort of initializer.
All you need to do is learn to differentiate between calling a method and defining a method. Defined methods will not be automatically executed.
The reason it is so is because of the way a class is built by Ruby: Every instance method definition inside a Ruby class gets defined first, during the top-down parsing. Then when you invoke each method it just matters whether its defined or not and not how its ordered.
Having said that order is important if you are redefining a method below. Then precedence will be given to lower definition.

How to organize directories and files for nested class

I have classes like this:
require 'active_support/core_ext'
class Shelf
def initialize
#books = {}
end
def book(code: code)
#books[code] if #books.has_key?(code)
#books = Book.new(code: code)
end
end
class Shelf::Book
def initialize(code: code)
#code = code
end
end
It works fine, if I write it in a file.
I want to separate classes into two files shelf.rb and shelf/book.rb, but when I write require_relative 'shelf/book' in shelf.rb then it fails because class Shelf is not defined yet.
How should I organize files and directories?
Or am I using nested class in completely wrong way?
Read this for naming convention of an *.rb file. According to it if you have a class Shelf:
class Shelf
end
then your file name should be shelf.rb, and if you have a class named: Shelf::Book, then file name book.rb should be within shelf directory(of course this is not a constraint or mandatory to have it in shelf directory, but it's a good convention to follow since any other developer would be able to locate your file book.rb easily):
class Shelf::Book
end
But, your problem is that how you require Shelf::Book, in Shelf, for that you need to call require_relative 'shelf/book' inside Shelf class definition, since Ruby will not know about Shelf being a class prior to it. Like this:
class Shelf
require_relative 'shelf/book'
end
However, if you don't want Shelf class's definition to throw error irrespective of where you use line: require_relative 'shelf/book' then change you shelf/book.rb to something like this:
class Shelf
class Book
def initialize(code: code)
#code = code
end
end
end
Because here Ruby opens up/create a class Shelf and won't throw this error:
`': uninitialized constant Shelf (NameError)
You can write another file which does not contain class definitions but which will be used tu run your program/script.
At the top of this file
require_relative 'shelf'
require_relative 'shelf/book'
#more code instructions
You don't need to require 'shelf/book' in shelf.rb

Module loading issue in Ruby

I have a situation where I can access a module's functions from one file but not another. These files are both in the same directory. I'll try to recreate the code the best I can:
Directory Structure:
init.rb
lib/FileGenerator.rb
lib/AutoConfig.rb
lib/modules/helpers.rb
lib/AutoConfig.rb
#!/usr/bin/env ruby
require 'filegenerator'
require 'modules/helpers'
class AutoConfig
include Helpers
def initialize
end
def myFunction
myhelper #here's the module function
end
def mySecondFunction
FileGenerator.generatorFunction # call to the FileGenerator
end
end
lib/FileGenerator.rb
#!/usr/bin/env ruby
require 'modules/helpers'
class FileGenerator
include Helpers
def initialize
end
def self.generatorFunction
myhelper #here's the module function that doesn't work
end
end
lib/modules/helper.rb
#!/usr/bin/env ruby
module Helpers
def myhelper
#Do Stuff
end
end
The AutoConfig file is the main workhorse of the app. When it calls to the myhelper module function it gives me no problems what-so-ever. The AutoConfig part way through calls the FileGenerator.generatorFunction.
The FileGenerator.generatorFunction also contains this same module function, but for some reason when I run the program I get the following error:
filegenerator.rb:26:in `generatorFunction': undefined method `myhelper' for FileGenerator:Class (NoMethodError)
I've been at this now for several hours trying many different combinations and can't figure out where I'm going wrong. Any help would be appreciated.
generatorFunction is a class method. It doesn't see instance-level methods. And myhelper (brought in by include Helpers) is an instance method. To remedy that, you should extend Helpers instead. It works like include, but makes class methods.
class FileGenerator
extend Helpers
end
BTW, the name generatorFunction is not in ruby style. You should name methods in snake_case (generator_function).

Resources