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
Related
I was having trouble understanding an error while unit testing my module, which is a mixin.
Suppose the mixin to be tested is module A:
require 'path/b'
module A
def methodA()
puts methodB.attr1
end
end
And it depends on another mixin B which was defined in a file at path/b.rb
module B
def methodB
return someObject #that has property 'attr1'
end
end
Now, we have a class to unit test module A
require 'path/moduleA'
class TestA
include Path::moduleA
end
describe 'test moduleA.methodA'
it 'does something'
testObject = TestA.new
testObject.methodA()
expect(....)
end
end
I get following error on running the rspec test
NameError:
undefined local variable or method `methodB' for #<TestA:0x00007f77a03db9a8>
I am able to resolve it by one of following ways:
including module B in module A
including module B in class TestA
Questions
I am not clear why include is required to get access to methodB in
module A and class TestA when 'require' was already added in module A.
My intention is to use methods of module B in module A, but not let users of module A access module B methods automatically.
resolution 1 above gives users of A, access to B's methods
resolution 2 forces users of A (user -> the unit test class in this example) to include A's dependency B directly, even though user is only interested in accessing A's methods.
Hence, both resolutions don't achieve what I want. Is there a way to achieve it?
I'm new to Ruby so may be it doesn't support this. I'm from Java background where I would model A and B as two classes, make an instance of B as field of A, then expose A's own public methods to users of A. But since they are mixins, I need to use modules in ruby.
Just to be very explicit: require / require_relative / load and include / extend / prepend have absolutely nothing whatsoever to do with each other.
The former three simply run a Ruby file. That's it. That is all they do. They differ in how and where they search for the file, but after they found the file, they don't do anything else than just execute it.
The latter three add a module to the ancestor chain. include essentially makes the module the superclass, extend is really the same as singleton_class.include (i.e. makes the module the superclass of the singleton class), and prepend inserts the module at the beginning of the ancestor's chain, i.e. actually before the class that it is prepended to.
require just tells ruby to read / load the code inside the ruby file. In this case it will just define the module. However in order for code inside a module to be included inside another module or class, you must include it inside the module or class. So you should just as you mentioned do:
require 'path/b'
module A
include B
def methodA()
puts methodB.attr1
end
end
You should not need to change your test with this since module A already includes module B. However this is not a very good OOP design pattern here. But hopefully you understand why now.
After more googling, I found the answer to my 2nd question using suggestion from:
https://makandracards.com/makandra/977-calling-selected-methods-of-a-module-from-another-module
so basically i did:
require 'path/b'
module A
module B_RequiredMethods
include Path::B
module_function :methodB
end
def methodA
puts B_RequiredMethods.methodB.attr1
end
end
In my case, B_RequiredMethods could be named properly to represent the method of B which would be exposed by it. Ideally, I would make those methods class level methods of B, but it is managed by some other team.
I would like to namespace my Ruby classes by putting them in a module. Indeed, this is a good idea if I decide to publish my Ruby gem so that the class names do not clash with existing classes in another gem. I know I can do the following for a class A::B:
module A
class B
end
end
However, the above is quite cumbersome in that I need to put all my class definitions into a single Ruby source file in order to scope them under the module. I would rather keep my class definitions in separate source files, much like a Java project, so how can I add classes to a module when they are all defined in separate files?
The accepted practice in this case is to wrap every file in module block
# a/b.rb
module A
class B
end
end
# a/c.rb
module A
class C
end
end
Btw. because of the way constant are resolved, it is advisable to use the long form I quoted above instead class A::B
(http://blog.honeybadger.io/avoid-these-traps-when-nesting-ruby-modules/).
I am working in live project and understanding the existing code.
like
Module Rating
def current_rating
# some code here
end
end
And called this method in included class.
How it works?
Actually I know about module but not used much. Say I am beginner for Module
module_function
Module methods that are declared as module_function will create copies of themselves as private instance methods in the class that includes the Module:
check this link: Hidden features of Ruby
I'm using the Sorcery library in a rails app. One of its modules is for external authentication, and I need to add a method to that module.
The existing code is here, I want to add the add_provider_to_user method from this patch.
So, I added a file to my lib/modules directory, which I've told rails to autoload. The file is called sorcery_extension.rb and it looks like this:
module Sorcery
module Controller
module Submodules
module External
module InstanceMethods
protected
# If user is logged, he can add all available providers into his account
def add_provider_to_user(provider)
provider_name = provider.to_sym
provider = Config.send(provider_name)
user_hash = provider.get_user_hash
config = user_class.sorcery_config
user = current_user.send(config.authentications_class.to_s.downcase.pluralize).build(config.provider_uid_attribute_name => user_hash[:uid], config.provider_attribute_name => provider)
user.save(:validate => false)
return user
end
end
end
end
end
end
This didn't work. I get undefined method error in my controller (where calling the other sorcery methods works fine).
So, my basic understanding of ruby is you can add methods to an object or module at any time... I think I've copied the nesting of the modules correctly in the file. Do I need to name the module file something different? I'm not really sure how to do this kind of thing, so any help is much appreciated. Thanks!
Your file is never required. You can double check this by typing in the console:
Sorcery::Controller::Submodules::External::InstanceMethods.method_defined?(:add_provider_to_user)
# => will return false, you want true
The reason is that auloading only happens when a constant is unknown in which case Rails will try to autoload it from the different autoloaded paths.
You have to require your file explicitly (e.g. from a file in initializer) and things will work as expected.
Rails convention on requires is that for every module it looks in a directory of the same name.
For your example
module Sorcery
module Controller
module Submodules
module External
module InstanceMethods
If you want to put the module in the lib directory. When it goes to "autorequire" Rails would be expecting it in this path
lib/sorcery/controller/submodules/external/instance_methods.rb
This is why its generally good convention to keep your module nesting shallow. ~ 2 levels deep.
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).