I'd like a very basic example of a tiny base program, that reads in two plugins and registers them. These two plugins hook into the base program in the same way in a unconflicting manner.
I'm very new to metaprogramming in any programming language for that matter, I'm not sure where to start.
i've been working on this very problem for a while now. i've tried a number of different ways to go about doing it and sought advice from a lot of people on it. i'm still not sure if what i have is 'the right way', but it works well and is easy to do.
in my case, i'm specifically looking at configuration and bringing in configuration plugins, but the principle is the same even if the terminology for mine is specific to cnfiguration.
at a very basic level, i have a Configuration class with nothing in it - it's empty. I also have a Configure method which returns the configuration class and lets you call methods on it:
# config.rb
class Configuration
end
class MySystem
def self.configure
#config ||= Configuration.new
yield(#config) if block_given?
#config
end
Dir.glob("plugins/**/*.rb").each{|f| require f}
end
MySystem.configure do |config|
config.some_method
config.some_value = "whatever"
config.test = "that thing"
end
puts "some value is: #{MySystem.configure.some_value}"
puts "test #{MySystem.configure.test}"
to get the some_method and some_value on the configuration class, I have the plugins extend the configuration object via modules:
# plugins/myconfig.rb
module MyConfiguration
attr_accessor :some_value
def some_method
puts "do stuff, here"
end
end
class Configuration
include MyConfiguration
end
and
# plugins/another.rb
module AnotherConfiguration
attr_accessor :test
end
class Configuration
include AnotherConfiguration
end
to load up the plugins, you only need one of code to look for the .rb files in a specific folder and 'require' them. this code can live anywhere as long as it's run right away when the file that contains it is loaded... i would probably put it in the class definition for MySystem or something like that to start with. maybe move it somewhere else when makes sense.
Dir.glob("plugins/**/*.rb").each{|f| require f}
run config.rb and you'll get output that looks like this:
do stuff, here
some value is: whatever
test that thing
there are a lot of options for implementing the various parts of this, but this should get you down the path.
It looks like this project may help: https://github.com/eadz/plugman
I haven't however found anything that will handle embedded (gem) dependencies however. Including Ruby files is straightforward but once your plugins start requiring other libraries then this simple model falls apart (either all the deps have to be installed with your application, or you need some other mechanism to get the plugin dependency gems into the process).
Related
I have a series of RSpec tests - each one living in their own files - that all need a specific object to be ran (shop).
Since setting up the shop object is complicated, I want to have a master file that builds the shop object and then passes it to all the RSpec test files in turn. The idea is that those writing the test scripts do not need to know anything about the setup step.
I could not figure out how to bring something from the outside world into an rspec test case. I have tried variations around the lines of this:
file master.rb:
describe "Testing tests/some_test.rb" do
before :all do
#shop = some_complex_operations
end
load 'tests/some_test.rb'
end
file tests/some_test.rb:
describe 'Some feature' do
it 'should know about #shop'
expect(#shop).not_to be nil
end
end
When I run rspec master.rb then some_test.rb fails.
I know what I outlined is probably a very wrong way to go about this, but I hope at least you get the idea.
How can I implement such a thing cleanly?
What you need is a global hook.
You can create a spec_helper and add there a before all global hook. For example:
RSpec.configure do |config|
config.before(:each) do
#shop = "in case you need a fresh instance of shop each time"
end
config.before(:all) do
#shop = "in case you need a single instance (i.e. you don't modify it)"
end
end
Then you should require this helper from each of your specs. The spec_helper can store any global hooks as well as helpers, or other dependencies for your tests.
When you are writing a test, it should test one thing. shop might be a very complex object, which many objects and methods interact with, but I should guess none of them are aware of the complexity of shop - each method is interested in some aspect of shop.
Therefore I suggest that instead of building a complex object for each test, make a double of it, make it expect what's relevant, and then simply stub the rest:
describe 'Some feature' do
let(:shop) { double('shop').as_nil_object }
let(:my_object) { MyClass.new(shop: shop) }
it 'should do something awesome with shop' do
expect(shop).to receive(:awesome_data).and_return(my_data: 'is_this')
expect(my_object.do_something_awesome).to eq 'how awesome is_this?'
end
end
I have an existing codebase of unit tests where the same classes are defined for each test, and a program that iterates over them. Something like this:
test_files.each do |tf|
load "tests/#{tf}/"+tf
test= ::Kernel.const_get("my_class")
Test::Unit::UI::Console::TestRunner.run( test )
While working on these tests, I've realized that ruby allows you to require classes with the same name from different files, and it merges the methods of the two. This leads to problems as soon as the class hierarchy is not the same: superclass mismatch for class ...
Unfortunately, simply unloading the class is not enough, as there are many other classes in the hierarchy that remain loaded.
Is there a way to execute each test in a different global namespace?
While I figure that using modules would be the way to go, I'm not thrilled with idea of changing the hundreds of existing files by hand.
--EDIT--
Following Cary Swoveland's suggestion, I've moved the test running code to a separate .rb file and am running it using backticks. While this seems to work well, loading the ruby interpreter and requireing all the libraries again has a considerable overhead. Also, cross-platform compatibility requires some extra work.
first I wanted to propose the following solution:
#main.rb
modules = []
Dir.foreach('include') do |file|
if file =~ /\w/
new_module = Module.new
load "include/#{file}"
new_module.const_set("StandardClass", StandardClass)
Object.send(:remove_const, :StandardClass)
modules << new_module
end
end
modules.each do |my_module|
my_module::StandardClass.standard_method
end
#include/first.rb
class StandardClass
def self.standard_method
puts "first method"
end
end
#include/second.rb
class StandardClass
def self.standard_method
puts "second method"
end
end
this wraps each class with it's own module and calls the class inside it's module:
$ruby main.rb
second method
first method
But as you answered there are references to other classes so modules can break them.
I checked this by calling the third class from one of the wrapped classes and got:
include/second.rb:5:in `standard_method': uninitialized constant StandardClass::Independent (NameError)
so probably using backticks is better solution but I hope my answer will be helpful for some other similar situations.
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 :)
In Ruby a class named Foo would be defined with class Foo, used via require 'foo' and would live in $:[0]/foo.rb or something like that.
But what about Foo::Bar? Would it be called with require 'foo/bar'? Would it live in $:[0]/foo/bar.rb? And how would it be defined?
I am very used to Perl, where for personal projects I would make nested classes like Project::User, Project::Text::Index, Project::Text::Search, etc. I would then make a file like Project/Text/Index.pm, which would start with package Project::Text::Index, and be called via use Project::Text::Index;.
Now I'm starting a project in Ruby and have no idea how to do this. For some reason none of the Ruby books or docs I've read mention perl-style hierarchical class naming. When they mention inheritance it's usually via a trivial made up example like class Foo < Bar which doesn't really help me. Yet I figure it must be possible to do what I'm attempting because Rails (just to take one example) has classes like ActionView::Helpers::ActiveModelFormBuilder.
You're combining a couple of concepts here which aren't really related, namely the load path, inheritance, and the scope resolution operator.
When requiring (or loading) files the argument to the require keyword is simply taken as a file path and appended to the load search path (the .rb extension is optional for require). Inheritance and nesting don't come into play here and any file can define anything it wants, e.g.:
require 'foo' # Looks for "foo.rb" in each of $:
require 'foo/bar' # Looks for "foo/bar.rb" in each of $:
Nested classes (and modules, variables, etc) are defined as expected but resolved with the scope resolution operator, e.g.:
class Foo
def foo; 'foo'; end
class Bar
def bar; 'bar'; end
end
end
Foo.new.foo # => "foo"
Foo::Bar.new.bar # => "bar"
Note that class nesting and inheritance are irrelevant to the location of the file from which they are loaded. There don't seem to be any explicit conventions for class/module structuring, so you're free to do what works for you. The Ruby Language page of the Programming Ruby book might be helpful too.
You need modules. If you want a class identified by Funthing::Text::Index in Funthing/Text/Index.rb and be required with require 'Funthing/Text/Index', you do this:
# file Funthing/Text/Index.rb
module Funthing
module Text
class Index
def do_the_twist()
puts "Let's twist again!"
end
end
end
end
Then use it like this:
require "Funthing/Text/Index"
c = Funthing::Text::Index.new
Note: the file hierarchy is not required, but (in my opinion) is best practice.
I am a starter with ruby, I searched that if someone else has asked similar question but was not able to find any. so I am asking it here.
I am trying my hand at modules in ruby.
I created a folder Project
inside Project folder, created a class One
class Project::One
include Project::Rest
end
inside Project folder, created a module Rest
module Project::Rest
def display
puts "in display"
end
end
but when I try to run the program(ruby one.rb) I get the
uninitialized constant Project (NameError)
Please help me
The problem is that you never actually define the Project constant. You have to define it before you can use it. Example:
# root.rb
module Project
end
require "project/test"
# project/test.rb
class Project::Test
end
You should then be able to run ruby root.rb. Another approach is to state the module in the namespace.
# root.rb
require "project/test"
# project/test.rb
module Project
class Test
end
end
With this example, you are able to run ruby project/test.rb as well, since the Project module is defined in that file.
And if you have multiple files defining the Project module, that's not a problem either. It won't be re-defined, it will always be the same module.
Both of these methods will define the Project module. Simply going Project::Test will not, however, define the module.
As a sidenote, Rails has a auto loader. If you're in a rails app, and use a certain folder structure, these kind of intermediate modules will be defined for you. Without Rails, though, you have to define them yourself.
The issue is that you're not nesting your classes/modules correctly. You have to declare a module with the module keyword, not merely by writing class Project::Class. Assuming you have this structure:
Project/
one.rb
rest.rb
then your files should look something like this:
# one.rb
require 'rest'
module Project
class One
include Project::Rest
end
end
# rest.rb
module Project
module Rest
def display
puts 'in display'
end
end
end
Note how the modules are nested in these examples.
If you have code in multiple files, you have to load those files before you can access what's in them. This is usually done with a require statement. I think what you want to do should look like this:
# one.rb
require 'rest'
module Project
class One
include Rest
end
end
# rest.rb
module Project
module Rest
def display
puts "in display"
end
end
end