Having the following files:
# ./app.rb
require_relative 'container'
require_relative 'contained'
# ./container.rb
class Foo
def initialize &block
puts block.call
end
end
# ./contained.rb
Foo.new do
"Hello, world!"
end
We can test and see everything's okay from a console:
$ ruby ./app.rb
Hello, world!
But I would like to simplify contained.rb by removing Foo.new do and end, keeping just the content of the block, by modifying app.rb.
In this quest, I came to this result:
# ./app.rb
require_relative 'container'
require_relative 'contained'
Foo.new do
eval File.open('contained.rb').read
end
# ./container.rb
class Foo
def initialize &block
puts block.call
end
end
# ./contained.rb
"Hello, world!"
With the same result:
$ ruby ./app.rb
Hello, world!
However I am not very proud of this code, mostly because of the eval method. Is there a best practice in this kind of case? What would you do?
Thanks for sharing your light.
The fact that you want to read from a separate file must be that you want to separate it from the main code and you want to occasionally change it. That category of things belongs to what would be called configuration. It is common these days to write that as a YAML file and read into Ruby using a yaml library.
Related
Is it possible to return the last evaluated object from a Ruby file?
Suppose I have a file like:
# app.rb
def foo
"Hello, world!"
end
foo
Then, I would expect something like this behavior:
# other_file.rb
require_relative!('foo') # => "Hello, world!"
Instead of the require_relative's true returned value, we fetch the last evaluated object of the required file.
Is there a way to have a require_relative!-like behavior?
It sounds like a bad practice. Ruby files are storages of ruby code, not data.
Idiomatically you should create a Ruby file with class or module, require it and call some function from it.
# foo.rb
module Foo
extend self
def foo
"bar"
end
end
# irb
require 'foo'
Foo.foo
#=> bar
Otherwise, as #sawa mentioned, you should read file as a string and eval it. Which is idiomatically wrong.
I tried running my code in a Ruby script from my terminal. Nothing happens when I run
ruby Main.rb.
# Main.rb
module Main
class MyClass
def initialize
puts "Hello World"
end
end
end
You need to instantiate your class first, as your puts command will not execute until you invoke MyClass#new. For example:
module Main
class MyClass
def initialize
puts "Hello World"
end
end
end
Main::MyClass.new
Hello World
=> #<Main::MyClass:0x007f9d92144308>
Because it's loading the Module and then doing nothing with it. It never gets instantiated (initialized), only defined.
You define the module like so:
module Main
class MyClass
def initialize
puts "Hello World"
end
end
end
And then initialize it by making a new MyClass object. (On the end of the same file)
test = Main::MyClass.new
Hello World
=> #<Main::MyClass:0x2979b88>
You can handle this in an even better way by only doing this when you run the file directly, not when it's loaded from another ruby file.
if __FILE__ == $0
test = Main::MyClass.new
puts test
end
This way you can do whatever you like when the code is run directly, for example, testing, but just load the module silently every other time.
When running the file directly, it will work as above, but when running this in IRB, you'll only see the following:
=> nil
In my lib folder I have billede.rb:
class Billede
require 'RMagick'
#some code that creates a watermark for a image
image.write(out)
end
How do I call/activate the class? Is the only way to change it to a Rake task?
You can't call a class directly. You have to call a method on that class. For example:
class Billede
def self.foobar
# some kind of code here...
end
end
Then you can call it via Billede.foobar
Perhaps you should read some documentation on basic ruby syntax before trying to do more complex things (such as manipulating images w/ Rmagick).
Code 'inside a class' is run just like any other code. If you have a Ruby file like this:
puts "Hello from #{self}"
class Foo
puts "Hello from #{self}"
end
and you run the file (either via ruby foo.rb on the command line or require "./foo" or load "foo.rb" in a script) it then you will see the output:
Hello from main
Hello from Foo
If you want to load a utility that 'does something' that you can then invoke from a REPL like IRB or the Rails console, then do this:
module MyStuff
def self.do_it
# your code here
end
end
You can require "./mystuff" to load the code, and when you're ready to run it type MyStuff.do_it
And, as you may guess, you can also create methods that accept arguments.
If you want to define a file that can be included in others (with no immediate side effects) but which also "does its thing" whenever the file is run by itself, you can do this:
module MyStuff
def self.run!
# Go
end
end
MyStuff.run! if __FILE__==$0
Now if you require or load this file the run! method won't be invoked, but if you type ruby mystuff.rb from the command line it will.
# in /lib/billede.rb
class Billede
def self.do_something(arg)
# ...
end
def do_anotherthing(arg)
# ...
end
end
# inside a model or controller
require 'billede'
Billede::do_something("arg")
# or
billede_instance = Billede.new
billede_instance.do_anotherthing("arg")
In a bunch of rspec rails unit specifications I do something like:
describe Foo do
[:bar, :baz].each do |a|
it "should have many #{a}" do
Foo.should have_many(a)
end
end
end
For cleaner code I'd rather do something like:
describe Foo do
spec_has_many Foo, :bar, :baz
end
So how do I write a helper method like spec_has_many() for inserting DSL code like rspec's it() method? If it were for an ordinary instance method I'd do something like:
def spec_has_many(model, *args)
args.each do |a|
define_method("it_should_have_many_#{a}") do
model.should have_many(a)
end
end
end
What would be the equivalent for defining rspec examples?
Ok, this took some messing around, but I think I got it working. It's a bit of metaprogramming hackery, and I personally would just use the first thing you described, but it's what you wanted :P
module ExampleMacros
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
# This will be available as a "Class Macro" in the included class
def should_have_many(*args)
args.each do |a|
# Runs the 'it' block in the context of the current instance
instance_eval do
# This is just normal RSpec code at this point
it "should have_many #{a.to_s}" do
subject.should have_many(a)
end
end
end
end
end
end
describe Foo do
# Include the module which will define the should_have_many method
# Can be done automatically in RSpec configuration (see below)
include ExampleMacros
# This may or may not be required, but the should_have_many method expects
# subject to be defined (which it is by default, but this just makes sure
# it's what we expect)
subject { Foo }
# And off we go. Note that you don't need to pass it a model
should_have_many :a, :b
end
My specs fail because Foo doesn't have a has_many? method, but both tests run, so it should work.
You can define (and rename) the ExampleMacros module in your spec_helper.rb file and it will be available for inclusion. You want to call include ExampleMacros in your describe blocks (and not any others).
To make all of your specs include the module automatically, configure RSpec like so:
# RSpec 2.0.0
RSpec.configure do |c|
c.include ExampleMacros
end
How can I overwrite the def method? But it's strange, cause I don't know from where the def method is defined. It's not Module, not Object, not BasicObject (of Ruby 1.9). And def.class don't say nothing ;)
I would like to use something like:
sub_def hello
puts "Hello!"
super
end
def hello
puts "cruel world."
end
# ...and maybe it could print:
# => "Hello!"
# => "cruel world."
Many thanks, for any ideas.
Who told you def is a method? It's not. It's a keyword, like class, if, end, etc. So you cannot overwrite it, unless you want to write your own ruby interpreter.
You could use alias_method.
alias_method :orig_hello, :hello
def hello
puts "Hello!"
orig_hello
end
You can use blocks to do some similar things like this:
def hello
puts "Hello"
yield if block_given?
end
hello do
puts "cruel world"
end
As others have said, def isn't a method, it's a keyword. You can't "override" it. You can, however, define a method called "def" via Ruby metaprogramming magic:
define_method :def do
puts "this is a bad idea"
end
This still won't override the def keyword, but you can call your new method with method(:def).call.
So, there you (sort of) have it.
Note: I have no idea why you'd ever want to define a method called def. Don't do it.