I have a conditional bit of code that only can be loaded under certain conditions. It's platform specific code.
module MyGem
module MyPlatformSpecificThing
#stuff
end
end
My current attempt at lazily requiring this is performed like:
module MyGem
class AClass
def DoSomething
if thing_is_true
require 'my_platform_specific_thing.rb'
MyGem::MyPlatformSpecificThing.init
#more stuff
end
#even more stuff
end
end
This seemed like a solid enough plan, unfortunately it is not working. This code results in an error:
uninitialized constant MyGem::MyPlatformSpecificThing (NameError)
The stack trace indicates the source of the error is the call to init within the DoSomething method.
I am not entirely sure why Ruby is giving me fits here. What am I doing wrong, and how should I be doing this?
edit:
For specifics, I'm referring to the Platform class located here. The above should help narrow down the details of what I'm referring to, but in case anyone wanted/needed to see the specific classes/modules I'm work with.
You've almost got it right. Remember that the require method loads relative to the load path. As you're making a gem you've added the gem lib directory to the load path. Change the require to:
require 'my_gem/bcm2835'
Or for PiPiper:
require 'pi_piper/bcm2835'
This should load only when a driver hasn't been set.
Your pseudo-code is bit incomplete and confusing, but I think this may be the problem. If I am mistaken, seeing your real code as complete as necessary could help.
Check this response as a reference.
Your example shows:
MyGem::MyPlatformSpecificThing.init
However, that format should have Module::Class.method. Your sample shows Module::Module.method.
The error message
uninitialized constant
is very specific in that it is recognizing the call as a constant, not a class.
The error you've specified...
uninitialized constant MyGem::MyPlatformSpecificThing (NameError)
... will only occur if the inner module (PiPiper::Bcm2835) is never actually defined; if the problem was with the actual call to the module function (init), you'd get a different error.
I notice you've commented out the require on line 10, platform.rb:
#require 'bcm2835.rb'
... and required the file elsewhere. Let me guess, this is solving your problem, no?
Taking another look, in its original form the require statement should have failed, unless you've already added the pi_piper directory to $LOAD_PATH. Try changing the lazy require to this:
require './bcm2835.rb'
But note that this will not work either if you change the working directory (Dir.chdir) at any point prior to executing this line.
Related
I tried to use a Refinement in IRB (v0.9.6, Ruby 2.3.0):
module Foo
refine Object do
def foo() "foo" end
end
end
using Foo # => RuntimeError: main.using is permitted only at toplevel
This is basically the exact setup from the documentation (which results in the same error).
What went wrong? How do I fix this?
It's either a bug or a misfeature of IRb. It is well-known that due to the pretty hackish way IRb is implemented, it does not behave correctly for all corner-cases.
The incompatibility probably everybody knows is that in Ruby, methods defined at the top-level become private instance methods of Object, whereas in IRb, they become public instance methods of Object. Another obvious behavioral difference is that in IRb, require_relative doesn't work, because it searches relative to the current file, but in IRb, there is no current file.
There are also some differences in what syntax gets accepted, I believe, and something to do with local variables and when exactly they are and aren't defined.
So, it is not inconceivable that there might also be some behavioral differences wrt. Refinements. In fact, I myself have encountered that error message, and running the exact same code outside IRb, either with ruby -e, from a file, or from a different REPL, always made it go away.
A quirk of Ruby's require is that, while in general, it will only load a file once, if that file is accessible via multiple paths (e.g. symlinks), it can be required multiple times. This causes problems when there are things like class-level metaprogramming, or in general a code that should only be executed once on file loading, getting executed multiple times.
Is there any way, from inside a Ruby class definition, to tell whether the class has been defined before? I thought defined? or Object.const_get might tell me, but from those it looks like the class is defined as soon as it's opened.
This is not an answer to your question in the second paragraph, but a solution to the issue in your first paragraph. Actually, you cannot avoid multiple file loads by checking whether a class was defined already.
Instead of doing:
require some_file_name
do:
require File.realpath(some_file_name)
By doing so, different symbolic links pointing to the same real file would be normalized to the same real file name, and hence multiple loading of them would be correctly filtered by require.
Cf. this question and the answer given there.
The real solution is that requiring that a piece of code is only executed once is bad design and you should fix that.
However, what you could do is simply set some flag that the code has already been executed and check that flag. E.g.:
class Foo
unless #__executed__
def bla; end
puts 'Test'
end
#__executed__ = true
end
I am confused about the difference between load 'file.rb' and require 'Module'. At Learn Ruby the Hard Way, the example of how to use a module is set up with two files (mystuff.rb and apple.rb):
mystuff.rb
module MyStuff
def MyStuff.apple()
puts "I AM APPLES!"
end
end
apple.rb
require 'mystuff'
MyStuff.apple()
However, when I run apple.rb, either in the Sublime Text console, or with ruby apple.rb, I get a Load Error. I have also tried require 'MyStuff', and require 'mystuff.rb', but I still get the Load Error.
So, I switched the first line of apple.rb to load 'mystuff.rb', which allows it to run. However, if I edit 'mystuff.rb' to be a definition of class MyStuff as opposed to a module MyStuff, there is no difference.
For reference, the Load Error is:
/Users/David/.rvm/rubies/ruby-2.0.0-p353/lib/ruby/site_ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in require': cannot load such file -- mystuff (LoadError)`
I've peeked into kernel_require.rb and looked at the require definition, but since I'm a Ruby Nuby (indeed, a programming newbie), it was a little overwhelming. Since Learn Ruby the Hard Way hasn't been updated since 2012-10-05, there've probably been some syntax changes for modules. Yes?
require searches a pre-defined list of directories, as discussed in What are the paths that "require" looks up by default?. It's failing because it can't find the mystuff.rb in any of those directories.
load, on the other hand, will look for files in the current directory.
As for:
However, if I edit 'mystuff.rb' to be a definition of class MyStuff as
opposed to a module MyStuff, there is no difference.
I'm not sure I understand what you mean by "no difference". If you mean that the require and load continue to fail and succeed, respectively, that makes sense, as the require failure is independent of the content of the file contents and the code you're testing behaves the same independent of whether Mystuff is a class or a vanilla module.
You can solve this easily by changing
require 'mystuff'
to
require_relative './mystuff'
I'm trying to run an example app from github. their app runs fine. which means that my setup is wrong, but I don't know what it is. this is my first time trying ruby.
what I did:
1- downloaded the code from github
2- made a gemfile:
source "https://rubygems.org"
ruby "2.0.0"
gem "sinatra", "1.4.4"
gem "haml", "4.0.3"
gem "sass", "3.2.12"
3- ran bundle install
4- when I run my .rb file (on heroku or on my computer), I get this error everytime:
/Library/Ruby/Gems/2.0.0/gems/sinatra-1.4.4/lib/sinatra/base.rb:1760:in `set_encoding': can't convert String to Hash (String#to_hash gives Symbol) (TypeError)
from /Library/Ruby/Gems/2.0.0/gems/sinatra-1.4.4/lib/sinatra/base.rb:1760:in `block in detect_rack_handler'
from /Library/Ruby/Gems/2.0.0/gems/sinatra-1.4.4/lib/sinatra/base.rb:1758:in `each'
from /Library/Ruby/Gems/2.0.0/gems/sinatra-1.4.4/lib/sinatra/base.rb:1758:in `detect_rack_handler'
from /Library/Ruby/Gems/2.0.0/gems/sinatra-1.4.4/lib/sinatra/base.rb:1420:in `run!'
from /Library/Ruby/Gems/2.0.0/gems/sinatra-1.4.4/lib/sinatra/main.rb:25:in `block in <module:Sinatra>'
--> I should signal that the github repo I'm trying to run is old (hasn't been touched since 2010)
It seems that this is an error in the used gem "sinatra".
Try to change the gemfile like this (without giving explicit version number), so that the application installs the latest version of the gems.
source "https://rubygems.org"
ruby "2.0.0"
gem "sinatra"
gem "haml"
gem "sass"
Probably there is a newer version of the sinatra gem.
Ok. so it turns out it wasn't my setup. (I tried using gems and ruby versions from 2009 and it didn't make the error go away either.)
I narrowed it down by gradually commenting things out in the script (since ruby wouldn't give me a line in my own files, only in sinatra files, I thought at first it wasn't due to my code).
--> it was a single line of code that, now that I commented it out in my script, made the error disappear forever with seemingly no negative side effects. (see code below).
If someone can briefly comment on that method (is it some kind of sinatra framework callback, for error handling a call to a nonexistent method? I assume this since it's never called in my files!) and why it could cause the error described in the question, I will pick their answer, otherwise I will pick this (my) answer. (just write anything you know or can find about this method and I'll pick your answer, Thanks).
def method_missing(methId)
method_name=methId.id2name.intern
if #lists.respond_to? method_name
#lists.send(method_name).pick
else
# method_name # line that caused the error in the sinatra source files.
end
end
================================================
so here's my own quick research on method_missing ... trying to learn ruby a bit here:
some info about method_missing:
http://www.alfajango.com/blog/method_missing-a-rubyists-beautiful-mistress/#/when-to-use-method-missing
http://rubylearning.com/blog/2010/10/07/do-you-know-rubys-chainsaw-method/
second link is a great intro.
basically method_missing is, yes, a kind of error handler for calling nonexistent methods. it's a ruby language thing (it calls method_missing when it can't find the method). so it's not tied to a framework, just to ruby.
besides simple error handling, one common usage for it is to be able define actions according to the (non-existent) method name (make up actions based on the nonexistent method name!), for example in the case where you have a great number of methods that do very repetitive things and you can deduce/base their behavior on their name.
a way to limit your pool of "methods you're going to make up behavior for, on the spot" is to have an if with a regular expression match (or in our case a match in a predefined list of items). if it's not matched, then you can for example throw an exception yourself or just return to see what happens next in the calling code.
so here in our code:
if the method name doesnt exist, they check if it exists in a list and act accordingly (they return a list, picked among #lists).
but if it is not found in #lists … they return that very method name, a string, that we didnt have a match for.
...but now the question is: --> how is the calling code dealing with that return value? (obviously not well, it seems to expect a hash or an array, not a method name string) a safe way to deal with "not finding a match for your nonexistent method" is to call super, to resume normal execution of the calling code (according to first link).
so one question remains: was it a good idea to return the method name? would returning the method name ever work? or was it really wrong, and super (or nothing) should be put in its place?
… my temporary conclusion is that --> you shouldn't return a list sometimes and a string some other times. so it would be a plain mistake, that surprisingly never caused problems or was never found in the original repo...
I am currently working through the Well Grounded Rubyist. Great book so far. I am stuck on something I don't quite get with ruby. I have two files
In ModuleTester.rb
class ModuleTester
include MyFirstModule
end
mt = ModuleTester.new
mt.say_hello
In MyFirstModule.rb
module MyFirstModule
def say_hello
puts "hello"
end
end
When I run 'ruby ModuleTester.rb', I get the following message:
ModuleTester.rb:2:in <class:ModuleTester>': uninitialized constant ModuleTester::MyFirstModule (NameError)
from ModuleTester.rb:1:in'
From what I have found online, the current directory isn't in the the namespace, so it can't see the file. But, the include statement doesn't take a string to let me give the path. Since the include statement and require statements do different things, I am absolutely lost
as to how to get the include statement to recognize the module. I looked through other questions, but they all seem to be using the require statement. Any hints are greatly appreciated.
You use require to load a file, not include. :-)
First, you have to require the file containing the module you want to include. Then you can include the module.
If you're using Rails, it has some magic to automagically require the right file first. But otherwise, you have to do it yourself.
You need to require the file before you can use types defined in it. *
# my_first_module.rb
module MyFirstModule
def say_hello
puts 'hello'
end
end
Note the require at the beginning of the following:
# module_tester.rb
require 'my_first_module'
class ModuleTester
include MyFirstModule
end
mt = ModuleTester.new
mt.say_hello
The require method actually loads and executes the script specified, using the Ruby VM's load path ($: or $LOAD_PATH) to find it when the argument is not an absolute path.
The include method, on the other hand actually mixes in a Module's methods into the current class. It's closely related to extend. The Well Grounded Rubyist does a great job of covering all this, though, so I encourage you to continue plugging through it.
See the #require, #include and #extend docs for more information.
* Things work a bit differently when using Rubygems and/or Bundler, but getting into those details is likely to confuse you more than it's worth at this point.