[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?
Related
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
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).
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!"