How to organize directories and files for nested class - ruby

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

Related

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

Allowing users to create plugins for my class, how to extend it correctly?

I have a class which I want other developers to be able to create modules to extend my class and give it more instance variables.
How can I automatically include modules in my class based on files in a particular folder? i.e. load all files and include them as modules for my class
How can I do this and somehow avoid method naming collisions?
I was thinking I could pass the name of the plugin in the initialize method, and do this:
class MyClass
def initialize(plugin_name=nil)
end
def method_missing(method_name)
"#{plugin_name}".send(method_name)
end
end
Would something like this work in theory, please help out in the method_missing as I have never written one before. I'm trying to basically specify a particular plugin name to avoid any name collisions.
Or should I, when including the plugins, output an error if there is a method collision?
Allowing external code to be melted in your class sounds dangerous and drives into names collisions as you guessed. It cans also drive into unexpected behavior, since you allow external code to mess with your class internals.
The best way to handle a plugin would be to encaspulate the behavior that would be replaced.
Let's take an example :
Given you're building a Logger, we can imagine that you can create plugins to specify where the logs will be wrote.
You can then create the following class :
class Logger
def initialize(store)
#store = store
end
def info(message)
#store.write(:info, message)
end
end
class StandardOutputStore
def write(level, message)
puts "#{level}: #{message}"
end
end
class FileStore
def initalize(filename)
#filename = filename
end
def write(level, message)
File.open(#filename, "a") do |file|
file.write("#{level}: #{message}")
end
end
end
Then you can you instanciate the right plugin this way :
logger = Logger.new(StandardOutputStore.new)
logger.warn "hello"
logger = Logger.new(FileStore.new("/tmp/log"))
logger.warn "hello"
This way of doing things provides modularity, more flexibility and is more solid than overloading things in your class with external code.
Loading modules as they are found in a directory is a matter of toying with Dir.glob and require. To explore what had been found under your plugin directory, you can force your users to write plugins in a module, like Foo::Plugins::FileStore and inspect which constants are present in the Foo::Plugins module after requiring the files. See http://www.ruby-doc.org/core-1.9.3/Module.html#method-i-constants for more informations.
Edit: As you don't have any interface to map to since you provides the methods to an ERB template, you could do the following :
Plugins are in the following form :
module Plugins
module Emoticons
def smile
":-)"
end
end
end
You can then load them with the following :
Dir["/plugins/*.rb"].each {|file| require file }
And include them, given TemplateMethods is a module included in the context of your ERB template.
Plugins.constants.each do |constant|
mod = Plugins.const_get constant
TemplateMethods.send(:include, mod)
end
Using send isn't very elegant, I'd suggest building a class method on TemplateMethods that encapsulate the whole thing, like TemplateMethods.load_plugins_method! The bang here would warn about the intrusive action done by the dynamic modules inclusion.
That should do the job :)

Rails equivalent of ApplicationController for models

I understand that application_controller.rb is the place to put all the methods, etc that you would like made available in all your controllers since they all inherit from this class. Great.
But what is the equivalent for Models? In other words, I want a place where I can create a couple of super classes that my models will inherit from.
For example, I have a method that searches different tables for entries in all CAPS via REGEXP in Mysql. I'd like to be able to create the method only once and call it for different tables/models.
What is the Rails way of doing this?
I thought I could create a class that would inherit from ActiveRecord::Base (as all models do) , put the methods in there and then inherit all my models from that class. But thought there would surely be a better way to do it.
Thanks.
Edit
Per Semyon's answer I'm editing the post to show the routes I am using. It works now:
# models/dvd.rb
require 'ModelFunctions'
class Dvd < ActiveRecord::Base
extend ModelFunctions
...
end
# lib/ModelFunctions.rb
module ModelFunctions
def detect_uppercase(object)
case object
...
where("(#{field} COLLATE utf8_bin) REGEXP '^[\w[:upper:]]{5,}' ").not_locked.reorder("LENGTH(#{field}), #{table}.#{field} ASC")
end
end
In config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
Take a look at mixins, for example here:
http://ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
In a Rails app you could create a module in the lib directory that defines your methods and then include it in your models.
EDIT: To be specific for your example, you're trying to define a class method. You can do this in a mixin like this:
module Common
module ClassMethods
def detect_uppercase(object)
case object
when 'dvd'
field = 'title'
...
end
where("(#{field} COLLATE utf8_bin) REGEXP '^[\w[:upper:]] {5,}').not_locked.reorder('LENGTH(title), title ASC')"
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
Now when you include Common in your model, that model's class will be extended to include the new class methods, and you should be able to call Dvd.detect_uppercase.
Put the reusable method in some module next to your Dvd class. You can move it in a separate file later.
# app/models/dvd.rb
module CaseInsensitiveSearch
def case_insensitive_search(field, value)
# searching field for value goes here
end
end
class Dvd
end
After extending a class with the module you can use case_insensitive_search on the class. Including the module will make case_insensitive_search an instance method which is not what you want.
class Dvd
extend CaseInsensitiveSearch
end
Dvd.case_insensitive_search("title", "foo")
And of course you can use it inside Dvd class.
class Dvd
def self.search(query)
case_insensitive_search("title", query)
end
end
Dvd.search("foo")
Now when you made sure it works, you will probably want to move it in a separate file and use it across multiple classes. Place it in lib/case_insensitive_search.rb and make sure you have this line in config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Now you can require it anywhere you want to use it:
require 'case_insensitive_search'
class Dvd
extend CaseInsensitiveSearch
end
The last thing I'd like to suggest. Create multiple modules with meaningful names. So instead of CommonModel have CaseInsensitiveSearch and so on.

How to load a unknown class in a module?

I have some rb files, all with the same structure:
class RandomName < FooBar
end
The randomname is a random class name which changes in each rb file but all inherits from Foobar.
how i can load all randomclass from there rb files?
I think there are 2 parts to the solution:
How to dynamically instantiate a class
a. Using String#constantize from ActiveSupport
klass = "SomeNamespace::SomeClassName".constantize
klass.new
b. Use Module#const_get (which doesn't handle namespaces)
klass = const_get(:SomeClassName)
klass.new
How to detect a class name
A convention followed widely in ruby is to name the file after the class that it contains, so random_name.rb would contain the RandomName class. If you follow this convention, then you could do something like:
Dir["/path/to/directory/*.rb"].each do |file|
require file
file_name = File.basename(file.path, '.rb')
# using ActiveSupport for camelcase and constantize
file_name.camelcase.constantize.new
end
I think you should explain what you are trying to accomplish. The approach you are taking seems unconventional and there may be a much more effective way of reaching your goal without doing all this loading of files and dynamic instantiation of classes with random names.
Remember, just because ruby lets you do something, it doesn't mean it's a good idea to actually do it!
you can define a method called inherited in the FooBar class. look here
class FooBar
def self.inherited(subclass)
puts "New subclass: #{subclass}"
end
end
Every time a subclass is created, you will get it in the callback. Then you can do whatever you want with all those subclasses.
I have a similar requirement, passing a class name in as a string. One trick with require is that it doesn't have to be at the start, so I prefer to only load the class I need.
I used eval because it doesn't have any Rails dependencies (I'm writing pure Ruby code here).
The following relies on convention (that the Class is in a file of the same name), but if you do know the class and file, this approach has the advantage of not requiring every file in a directory and only dynamically loading the one you need at the time you need it.
klass = "classname"
begin
# Load the file containing the class from same directory I'm executing in
require_relative klass # Or pass a local directory like "lib/#{klass}"
# Use eval to convert that string to a Constant (also capitalize it first)
k = eval(klass.capitalize).new
rescue
# Do something if the convention fails and class cannot be instantiated.
end
k.foo # Go ahead and start doing things with your new class.

Output Ruby class

This, will be a very strange question. And I really doubt it is possible. Some will call this stupid, and I wouldn't agree more. But it is for mere curiosity!
class MyClass
def initialize
print "Ha"
end
end
Is there a way to print the class file? I mean, create a .txt file, containing exactly the code above?
Sure. You can use the magic constant __FILE__ which contains the path to the file that you use it in:
class MyClass
def initialize
puts File.read(__FILE__)
end
end
This will print the contents of the file containing the definition of MyClass every time you create a MyClass object.

Resources