I've noticed in some gems, when you simply require 'some_gem', methods will appear (without any monkey patching to my knowledge). I've seen it in some gems like Sinatra, Rake, Rails, and many other helper libraries and such. How would one manage to accomplish this in ones own library?
Example:
require 'sinatra'
# Automatically recieve the 'get' method
get('/') { "I was monkeypatched or included automatically." }
If it is monkeypatching, what classes/modules are common for monkeypatching (other than String, Numeric, Array, etc).
Sinatra is essentially adding those as global methods. When you require sinatra, it extends the Object class with Sinatra::Delegator which is defined in sinatra/base.rb. Methods such as get and put are defined in base, and added via the delegator.
In addition to Beerlington's answer, Rails, for example, and specifically it's part ActiveSupport, uses exactly monkeypatching.
For example, declaration of blank? method from the ActiveSupport source (stripped):
class Object
def blank?
respond_to?(:empty?) ? empty? : !self
end
end
Also, very common approach to monkeypatch Kernel module to add methods that will be available everywhere:
# hello.rb
module Kernel
def say_hello
"Hello!"
end
end
And usage of it:
require 'hello.rb'
=> true
say_hello
=> "Hello!"
Related
I have a Ruby module in a file called my_module.rb:
module My_module
def my_module_method
puts 'inside my method'
end
end
In a file my_class.rb in the same folder, I have a class contained within the module.
module My_module
class My_class
def my_object_method
My_module.my_module_method
end
end
end
My_module::My_class.new.my_object_method => 'undefined method 'my_module_method''
I was not expecting this error. I assumed that Ruby would run into the line 'My_module.my_module_method' and search for a module called 'My_module' and a method within it called 'my_module_method.' This is what Java does, for example. However, Ruby does not do this. In order to get my_object_method to work, I have to write in my_class.rb:
require 'my_module.rb'
Why doesn't Ruby search for My_module when I call my_object_method? It seems obvious what it should search for and therefore redundant to require the programmer to explicitly write 'yes, Ruby, please allow me to make calls to module-wide methods.' What am I missing?
Ruby doesn't automatically load files. If you need a code from some file, you have to load it (by calling require) explicitly.
Thus, when you run "ruby my_class.rb" it loads only this file and you have to define dependencies between files by yourself.
You seem to have a misunderstanding of how to define a class method. In order to make your method call work, you could define it as def self.my_method_name.
In both classes and modules, methods work the same when you define them as class methods using self. or alternatively the class << self syntax. However instance methods (methods without the self.) work differently in these 2 cases. In classes, as you seem to understand, they're accessible once you instantiate the class using .new. In modules, they're only accessible if you include or extend.
See also:
difference between class method , instance method , instance variable , class variable?
http://www.rortuts.com/ruby/ruby-include-vs-extend/
Oh any by the way. Ruby doesn't enforce any convention where you have 1 file per class (named identically). You need to manually require files wherever you need them. Although there are some frameworks such as Rails which auto-require files, and enforce naming conventions.
I'm using ActiveRecord with Sinatra instead of Rails, and I want to use fixtures in my tests. The documentation for ActiveRecord's FixtureSet says that you have to use fixture_path to tell it where the fixture files are:
placed in the directory appointed by ActiveSupport::TestCase.fixture_path=(path)
How can I write to that setting? I tried #fixture_path and ##fixture_path, but both of them left the value nil when FixtureSet tried to read it.
Here's the only thing I could get to work, but it can't possibly be right:
# test_helper.rb
require_relative '../app'
require 'minitest/autorun'
require 'active_record'
ActiveRecord::Base.establish_connection(:test)
#Set up fixtures and such
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
include ActiveRecord::TestFixtures::ClassMethods
class << self
def fixtures(*fixture_set_names)
self.fixture_path = 'test/fixtures'
super *fixture_set_names
end
end
self.use_transactional_fixtures = true
self.use_instantiated_fixtures = false
end
The full source code is posted as a small demo project for ActiveRecord and Sinatra.
I tried to leave this as a comment on your answer, but it got too long so I thought I might as put it in an answer.
The reason #fixture_path and ##fixture_path didn't work is that fixture_path is an ActiveSupport class attribute, which is like a Ruby attr_accessor except it's defined as a singleton method on the class. You can see where the fixture_path attribute is defined with class_attribute :fixture_path in the ActiveRecord::TestFixtures module source.
Class attributes are part of ActiveSupport and not native to Ruby. You can read more about them in the Active Support Core Extensions Rails Guide and in the API docs, and see how class_attribute works in the Rails source. As you can see,
the value is stored in the instance variable "##{name}" (e.g. #fixture_path), but that happens inside a singleton method, which means it's an instance variable on the singleton class and you can only access it from within the singleton class.
That's all a little bit moot, though, because the point of attributes (and feel free to disregard this if it's old news to you) is that they allow you to keep instance variables private and change your implementation without breaking code that subclasses or includes your code. When an attribute reader or writer exists, you should always use it instead of accessing the instance variable directly, because at some point the implementation might change and the attribute methods could be replaced by methods with more complex logic, and accessing the instance variable directly will no longer produce the same results as using the attribute reader and writer.
As you discovered, you need to use self.fixture_path = instead of fixture_path = because in the latter case Ruby assumes you want to assign to a local variable.
I can't believe I didn't see this, but I didn't. I had to use self, just like the settings for transactional fixtures and instantiated fixtures.
# test_helper.rb
require_relative '../app'
require 'minitest/autorun'
require 'active_record'
ActiveRecord::Base.establish_connection(:test)
#Set up fixtures and such
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
include ActiveRecord::TestFixtures::ClassMethods
self.fixture_path = 'test/fixtures'
self.use_transactional_fixtures = true
self.use_instantiated_fixtures = false
end
The trick is understanding the meaning of self in a class definition; it refers to the class, not an instance of the class. I guess when I'm monkey patching ActiveSupport::TestCase, that's the only way to set a class variable. For some reason #fixture_path and ##fixture_path don't work.
I have a capybara monkey patch to deal with jquery-ui, which works pretty well running on Ubuntu... although when moving to windows I get the following error (all dependency gems were installed successfully):
Undefined method 'delegate' for capybara::dsl::module
The line of code that this occurs is:
module Capybara::DSL
delegate :datepick, :datetimepick, :timepick, to: :page
end
any ideas of what this could be? a bit lost of why this error is shown just by switching OS...
In standard ruby delegation is handled by the module Forwardable. You need to require and then extend Forwardable to access these methods like so:
require 'forwardable'
module Capybara::DSL
extend Forwardable
#notice syntax is accessor, *methods
def_delegators :page, :datepick, :datetimepick, :timepick
end
The type of delegation you are trying to use right now is part of active support Module Class. If you would like to use this syntax then do so like this:
require 'active_support/core_ext/module'
module Capybara::DSL
#active_support syntax allows a to: element in the hash to act as the accessor
delegate :datepick, :datetimepick, :timepick, to: :page
end
I do not know where Sinatra methods (like get or params) are defined. According to base.rb, they are static parts of Sinatra's Base class. How can I call them anywhere by just writing get? Shouldn't I write something like Sinatra::Base.get instead? And how can I define things like that by myself?
The answers can be found here: https://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb
When you do Sinatra in its simple mode, all methods like get or set or post are delegated through Sinatra::Delegator, which is defined here: https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1977 and is mixed into global scope inside main.rb
Leaving all Sinatra tricks alone, for your own module you can achieve the effect with really simple code:
module MyMixin
def testme
puts 'testme'
end
end
extend MyMixin
testme # => testme
[SOLVED: See my comment below]
I've created a Ruby Gem to connect to my application's API: my_app_api. I'd like to use it like so: MyAppAPI::Foo.bar(). However, I get:
NameError: uninitialized constant MyAppAPI
I know the standard way to call/name this would be MyAppApi::Foo.bar(), but I'd prefer to keep with acronym class naming conventions. How do I specify/load the module?
For reference, the class looks like this:
module MyAppAPI
class Foo < ActiveResource::Base
extend MyAppAPI
self.site = 'http://localhost:3000/api/'
self.format = :json
class << self
def bar
return 'huzzah!'
end
end
end
end
And the my_app_api.rb file looks like this:
require "rubygems"
require 'active_resource'
require 'my_app_api/foo'
Have you tried loading the gem the normal way?
require 'my_app_api'
MyAppAPI::Foo.bar()
The constant name MyAppAPI is fine and is not the cause of the problem. There are tons of Ruby core classes/modules that have acronyms in their names:
http://www.ruby-doc.org/core-1.9.3/GC.html
http://www.ruby-doc.org/core-1.9.3/RubyVM.html
http://ruby-doc.org/stdlib-1.9.3/libdoc/csv/rdoc/CSV.html
Try declaring the empty module in my_app_api.rb after your require statements:
module MyAppAPI
end
This may help if you're relying on a dynamic class and module loading mechanism (like Rails uses).
I assume your app is explicitly calling require "my_app_api". What kind of app is this, and where are you doing the require?