I have a setup in the lib directory like so:
lib/
copy_process.rb
copy_process/
processor.rb
The copy_process.rb and processor.rb contain the module definition CopyProcess. The copy_process.rb defines the CopyFile class as well:
module CopyProcess
class CopyFile
end
end
The processor.rb is structured like so:
module CopyProcess
class Processer
end
end
In one of its methods, it creates a new copy file object:
def append_file_if_valid(file_contents, headers, files, file_name)
unless headers
raise "Headers not found"
else
files << CopyProcess::CopyFile.new()
end
end
When I used these files as part of a command line ruby program, it worked fine. However, i started putting it into a rails app, and I have written cucumber/capybara tests to hit the buttons and so forth where this is used. I initialize a Processor object from one of my AR models, and call the above method a few times. It cannot seem to find the CopyFile class, even though I have the following code in my application.rb
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
Any ideas?
===============================================================
Edit Above was solved by extracting the copy file class into it's own file under lib.
I now have another issue:
The CopyFile class refers to module-level helper methods that sit in lib/copy_process.rb, like so:
module CopyProcess
# Gets the index of a value inside an array of the given array
def get_inner_index(value, arr)
idx = nil
arr.each_with_index do |e, i|
if e[0] == value
idx = i
end
end
return idx
end
def includes_inner?(value, arr)
bool = false
arr.each { |e| bool = true if e[0] == value }
return bool
end
# Encloses the string in double quotes, in case it contains a comma
# #param [String] - the string to enclose
# #return [String]
def enclose(string)
string = string.gsub(/\u2019/, '’')
if string.index(',')
return "\"#{string}\""
else
return string
end
end
end
When I run my cucumber tests, i get the following error:
undefined method `includes_inner?' for CopyProcess:Module (NoMethodError)
./lib/copy_process/copy_file.rb:64:in `set_element_name_and_counter'
Which refers to this method here:
def set_element_name_and_counter(element_names, name)
if !CopyProcess::includes_inner?(name, element_names)
element_names << [name, 1]
else
# if it's in the array already, find it and increment the counter
current_element = element_names[CopyProcess::get_inner_index(name, element_names)]
element_names[CopyProcess::get_inner_index(name, element_names)] = [current_element[0], current_element[1]+1]
end
element_names
end
I also tried moving the copy_file.rb and other files in the lib/copy_process/ directory up a level into the lib directory. I then received the following error:
Expected /Users/aaronmcleod/Documents/work/copy_process/lib/copy_file.rb to define CopyFile (LoadError)
./lib/processor.rb:48:in `append_file_if_valid'
The line that the error states creates an instance of CopyFile. I guess rails doesn't like loading the files in that fashion, and for the former setup, I think the copy_file.rb is having issues loading the rest of the module. I tried requiring it and so forth, but no luck. You can also find my most recent code here: https://github.com/agmcleod/Copy-Process/tree/rails
First config.autoload_paths += %W(#{config.root}/lib) should be sufficient. This tells rails to start looking for properly structured files at /lib.
Second, I think that you're running into issues because CopyFile isn't where rails expects it to be. As far as I know your setup 'should' work but have you tried seperating CopyFile out into its own file under the copy_process folder? My guess is that since the copy_process folder exists, it is expecting all CopyProcess::* classes to be defined there instead of the copy_process.rb.
EDIT: You may consider opening another question, but the second half of your question is a different problem entirely.
You define methods in your module like so,
module X
def method_one
puts "hi"
end
end
Methods of this form are instance methods on the module, and they have very special restrictions. For instance, you can not access them from outside the module definition (I'm skeptical how these worked previously). Executing the above gives
> X::method_one
NoMethodError: undefined method `method_one' for X:Module
If you want to access these methods from other scopes you have a few options.
Use Class Methods
module X
def self.method_one
puts "hi"
end
end
X::hi #=> "hi"
Use Mixins
module X
module Helpers
def method_one
puts "hi"
end
end
end
class CopyFile
include X::Helpers
def some_method
method_one #=> "hi"
self.method_one #=> "hi"
end
end
Related
I want to write a simple erb template generator to parse stored erb templates from the views using Generator module. I call the Generator from the rails controller to generate it's singleton instances and pass it the WallController by self pointer.
require 'generator'
class WallController < ApplicationController
def index
header = File.read 'app/views/application-header.html'.freeze
#instances = {header: header}
# Load view generators
Generator.generate_instances self
end
end
The first thing Generator.generate_instances actually attempts to do is to copy the WallController instance variables (hence the self pointer) to perform correct parsing of the erb templates. Then it generates methods returning erb resulted text.
require 'erb'
module Generator
def self.generate_instances environment
# Mimic class environment
if environment.superclass == ApplicationController
environment.instance_variables.each do |v|
puts "Copy instance variable '#{v}' from #{environment.name} to #{self.name}"
value = environment.instance_variable_get(v)
self.send :instance_variable_set, v, value
end
end
# Parse the ERB templates
templates = #instances
return 0 if !templates.is_a?(Hash) or templates.empty?
templates.keys.each.with_index do |key, index|
define_singleton_method key do
ERB.new(templates.values[index]).result
end
end
end
end
Usage of Generator interface will look like this:
<%=== Generator.header %>
I am new to rails but I have found out that rails controller's included files are limited to a single static structure. I didn't manage either to overwrite class Object or class Class singleton methods which could be helpful.
However, after running the above example the instance variables of WallController return the WallController class address in stead of values defined by WallController.index.
undefined method `empty?' for #<WallController:0x000000000a1f90>
Is there a correct way to distribute a rails controller instance variables among other controllers? If not, why are regular instance copy not working?
If I had to write it in ruby, that would be easy:
module Y
def self.generate_environment environment
environment.instance_variables.each do |v|
puts "Copy #{v} from #{environment.name} to #{self.name}"
value = environment.instance_variable_get v
self.instance_variable_set(v, value)
end if environment.class == Class
puts "Retrived string: #{#hello}"
end
end
class X
def self.index
#hello = 'Hello, World!'
Y.generate_environment self
end
end
X.index
This problem may be solved with viewcomponent, which allows for standard ruby code for the view controller. Also solves the problem of dividing the view code to smaller reusable components in reasonable speed.
To use the viewcomponent gem first include it to your Gemfile.
gem 'view_component', require: 'view_component/engine'
After updating your gems with bundle install, you will also need to restart your server if it's running, to apply the new gem.
Then generating the component is similar in usage to other rails generators. The first argument is a component name and the second is a component argument.
rails generate component Header site_id
Now I focus on files generated in app/component directory, view and controller code. This will simply be the controller to create the header snippet of the view.
Inside of app/component/header_component.rb can be encapsulated all the code from WallController related to the header view.
class HeaderComponent < ViewComponent::Base
def initialize(site_id:)
puts "Rendering header component for site: #{site_id}"
# Load site elements
#site = Site.find site_id
#menu_items = []
Site.all.each.with_index do |site, index|
#menu_items.push site.title => site.link
end
end
end
Similarly, put all the header view erb code to the app/component/header.html.erb.
The finished component can be generated from the view using rails render:
<%= render HeaderComponent.new(site_id: 1) %>
I have created a new library file sampler.rb inside the lib folder. Consider this as the content of the file
module Sampler
def sample_tester
"test"
end
end
I have included it in the application_controller and added a require statement in the config\initializers. When I try to access the method sample_tester from my controllers, I get the following error
undefined local variable or method `sample_tester` for #<BlogsController:0xb8fbac8>
Am I missing something?
Since it doesn't look like you are creating an instance of this, my first guess is that you need to define it as a class method so that it can be called like this: Sampler.sample_tester.
In your file you could do it one of two ways:
# first way
module Sampler
def self.sample_tester
"test"
end
end
# second way
module Sampler
class << self
def sample_tester
"test"
end
end
The second way is nicer if you want to define a number of class methods.
if you want to have your module method defined as a class method you need to use extend instead of include:
module Mod
def bla
puts "bla"
end
end
class String
include Mod
end
String.bla rescue puts $! # => undefined method `bla' for String:Class
class String
extend Mod
end
puts String.bla # => bla
I am using jruby to run bunch of ruby scripts, though I am using pure ruby part of it.
It sometimes gets difficult to follow from output what exactly is happening or where exactly something went wrong.
I wanted to get something like this in my std out for every method:
entered in method A
out of method A
Now I can surely go and put those comments in every method ..which feels very wrong. Is there a way to run ruby in a little verbose more to get this information in my log. Hopefully I would avoid using a lot of gems etc .. since these are on some managed servers and I will have to spend some time to just get more s/f on it. Hoping something would be avail as part of jruby itself
Thanks!
You could use this code:
module InOutHook
module ClassMethods
def setup_hooks(*syms)
syms.each do |sym| # For each symbol
str_id = "__#{sym}__hooked__"
unless private_instance_methods.include?(str_id)
alias_method str_id, sym # Backup original method
private str_id # Make backup private
define_method sym do |*args| # Replace method
puts ">>> #{self.class}\##{sym} >>>"
ret = __send__ str_id, *args # Invoke backup
puts "<<< #{self.class}\##{sym} >>>"
ret
end
end
end
end
end
def InOutHook.included(base)
base.extend(ClassMethods)
end
end
class TestClass
def test1
puts "test!"
end
def test2(v)
puts "Value is #{v}"
end
include InOutHook
setup_hooks(:test1, :test2)
end
# works on existing classes too:
class Array
include InOutHook
setup_hooks(:[])
end
tc = TestClass.new
tc.test1
tc.test2(10)
ary = [1,2,3]
puts ary[1..2]
In case you want to add a hoot to every method, just add a splat asterisk:
setup_hooks(*[].methods)
I have a module saved in /lib as test_functions.rb that looks like this
module TestFunctions
def abc
puts 123
end
end
Going into ruby script/runner, I can see that the module is loading automatically (good ol' convention over configuration and all that...)
>> TestFunctions.instance_methods
=> ["abc"]
so the method is known, let's try calling it
>> TestFunctions.abc
NoMethodError: undefined method `abc' for TestFunctions:Module from (irb):3
Nope. How about this?
>> TestFunctions::abc
NoMethodError: undefined method `abc' for TestFunctions:Module from (irb):4
Test
Nope again.
defined?(TestFunctions::abc) #=> nil, but
TestFunctions.method_defined? :abc #=> true
Like I said at the top, I know I'm being dumb, can anyone de-dumb me?
If you want Module-level functions, define them in any of these ways:
module Foo
def self.method_one
end
def Foo.method_two
end
class << self
def method_three
end
end
end
All of these ways will make the methods available as Foo.method_one or Foo::method_one etc
As other people have mentioned, instance methods in Modules are the methods which are available in places where you've included the Module
I'm going to try to summarise the various answers myself, since each had something valuable to say, but none really got to what I now realise is probably the best response:
I was asking the wrong question because I was doing it wrong.
For reasons I can no longer explain, I wanted a set of completely stand-alone functions in a library, which represented methods I was trying to DRY out of my classes. That can be achieved, using things like
module Foo
def self.method_one
end
def Foo.method_two
end
class << self
def method_three
end
end
def method_four
end
module_function :method_four
end
I could also include my module, either within a class, in which case the methods become part of the class or outside, in which case they are defined on whatever class I'm running inside (Object? Kernel? Irb, if I'm interactive? Probably not a great idea, then)
The thing is, there was no good reason not to have a class in the first place - I'd somehow got on to a train of thought that took me down an seldom-used and frankly slightly weird branch line. Probably a flashback to the days before OO became mainstream (I'm old enough that up to today I've spent a lot more years writing procedural code).
So the functions have moved into a class, where they seem pretty happy, and the class methods thus exposed are being cheerfully used wherever necessary.
You can also use module_function like so:
module TestFunctions
def abc
puts 123
end
module_function :abc
end
TestFunctions.abc # => 123
Now you can include TestFunctions in class and call "abc" from within TestFunctions module.
I messed with this for a while and learned several things. Hopefully this will help someone else out. I am running Rails 3.2.8.
My module (utilities.rb) looks like this and is in the /lib directory of my rails app:
module Utilities
def compute_hello(input_string)
return "Hello #{input_string}"
end
end
My test (my_test.rb) looks like this and is in the /test/unit directory of my rails app:
require "test_helper"
require "utilities"
class MyTest < ActiveSupport::TestCase
include Utilities
def test_compute_hello
x = compute_hello(input_string="Miles")
print x
assert x=="Hello Miles", "Incorrect Response"
end
end
Here are a few things to note: My test extends ActiveSupport::TestCase. This is important because ActiveSupport adds /lib to the $LOAD_PATH. (seehttp://stackoverflow.com/questions/1073076/rails-lib-modules-and)
Secondly, I needed to both "require" my module file, and also "include" the module. Lastly, it is important to note that the stuff that gets included from the module essentially gets placed in the test class. So... be careful that the module that you include doesn't start with "test_". Otherwise, Rails will attempt to run your module method as a test.
You can't call a method in a Module directly. You need to include it in a class. Try this:
>> class MyTest
>> include TestFunctions
>> end
=> MyTest
>> MyTest.new.abc
123
=> nil
You need to include the module
include Testfunctions
Then 'abc' will return something.
You need to prefix your function with the module name because modules are not classes:
Your /lib/test_functions.rb:
module TestFunctions
def TestFunctions.abc
puts 123
end
end
Your code using the module method:
require 'test_functions'
TestFunctions.abc
Today you can do it using module_function notation.
module TestFunctions
def abc
puts 123
end
end
Now TestFunctions.abc prints "123"
A little more about module_function: https://apidock.com/ruby/Module/module_function
Try this code:
service = Class.new { extend TestFunctions }
service.method_four
I'm working on a Rails app and am looking to include some functionality from "Getting the Hostname or IP in Ruby on Rails" that I asked.
I'm having problems getting it to work. I was under the impression that I should just make a file in the lib directory, so I named it 'get_ip.rb', with the contents:
require 'socket'
module GetIP
def local_ip
orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
UDPSocket.open do |s|
s.connect '64.233.187.99', 1
s.addr.last
end
ensure
Socket.do_not_reverse_lookup = orig
end
end
I had also tried defining GetIP as a class but when I do the usual ruby script/console, I'm not able to use the local_ip method at all. Any ideas?
require will load a file. If that file contains any class/module definitions, then your other code will now be able to use them. If the file just contains code which is not in any modules, it will get run as if it were in the same place as your 'require' call (like PHP include)
include is to do with modules.
It takes all the methods in the module, and adds them to your class. Like this:
class Orig
end
Orig.new.first_method # no such method
module MyModule
def first_method
end
end
class Orig
include MyModule
end
Orig.new.first_method # will now run first_method as it's been added.
There's also extend which works like include does, but instead of adding the methods as instance methods, adds them as class methods, like this:
Note above, how when I wanted to access first_method, I created a new object of Orig class. That's what I mean by instance method.
class SecondClass
extend MyModule
end
SecondClass.first_method # will call first_method
Note that in this example I'm not making any new objects, just calling the method directly on the class, as if it had been defined as self.first_method all along.
So there you go :-)
You haven't described how you're trying to use the method, so I apologize in advance if this is stuff you already know.
The methods on a module never come into use unless the module is included into a class. Instance methods on a class require there to be an instance of the class. You probably want a class method instead. And the file itself should be loaded, generally through the require statement.
If the following code is in the file getip.rb,
require 'socket'
class GetIP
def self.local_ip
orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
UDPSocket.open do |s|
s.connect '64.233.187.99', 1
s.addr.last
end
ensure
Socket.do_not_reverse_lookup = orig
end
end
Then you should be able to run it by saying,
require 'getip'
GetIP.local_ip
require and include are two different things.
require is to strictly load a file once from a load path. The loadpath is a string and this is the key used to determine if the file has already been loaded.
include is used to "mix-in" modules into other classes. include is called on a module and the module methods are included as instance methods on the class.
module MixInMethods
def mixed_in_method
"I'm a part of #{self.class}"
end
end
class SampleClass
include MixInMethods
end
mixin_class = SampleClass.new
puts my_class.mixed_in_method # >> I'm a part of SampleClass
But many times the module you want to mix in is not in the same file as the target class. So you do a require 'module_file_name' and then inside the class you do an include module.