I have two classes called Person and Animal that have a lot of methods in them. I have a need to export some data from these classes to CSV. I also want to refactor the CSV specific code out of the class files themselves (and move it to modules). So, I created directories called person/ and animal/, and put a csv_export.rb file in each (with a CSVExport Module).
person.rb
person/csv_export.rb
animal.rb
animal/csv_export.rb
CSV exported data is different enough for the two classes that I can't just have one common csv_export.rb file (for now).
Example of person.rb:
class Person
include CSVExport
# numerous methods...
end
Example of csv_export.rb:
module CSVExport
def to_csv
# some logic
end
end
My question is what's the correct way to namespace these two modules so they load properly? And what is the proper way to require them within Person/Animal (assuming the files aren't loaded by something like Rails)?
I would call the modules Person::CSVExport and Animal::CSVExport. You can require them (from each individual file) like this:
require File.join(File.dirname(__FILE__), 'animal', 'csv_export.rb')
require File.join(File.dirname(__FILE__), 'person', 'csv_export.rb')
This doesn't work from inside irb (at least in Ruby 1.9.2p290, which I am running), but it does work if a Ruby program is run from a file.
You made a good point; if you load (for example) animal/csv_export.rb at the top of animal.rb, at that point Animal is an undefined constant. You can get around that like this:
class Animal
module CSVExport
# contents
end
end
Note that if you can, a better approach might be to build some low-level utility methods for yourself, which could make it possible to code the CSV export logic at a higher level of abstraction, and make that code short enough to go in animal.rb and person.rb without overwhelming them. I don't know what those utilities would be exactly; I'd have to see the code to offer specific suggestions.
Related
I decided to dig into the ActiveRecord code for Rails to try and figure out how some of it works, and was surprised to see it comprised of many modules, which all seem to get included into ActiveRecord::Base.
I know Ruby Modules provide a means of grouping related and reusable methods that can be mixed into other classes to extend their functionality.
However, most of the ActiveRecord modules seem highly specific to ActiveRecord. There seems to be references to instance variables in some modules, suggesting the modules are aware of the internals of the overall ActiveRecord class and other modules.
This got me wondering about how ActiveRecord is designed and how this logic could or should be applied to other Ruby applications.
It is a common 'design pattern' to split large classes into modules that are not really reusable elsewhere simply to split up the class file? Is it seen as good or bad design when modules make use of instance variables that are perhaps defined by a different module or part of the class?
In cases where a class can have many methods and it would become cumbersome to have them all defined in one file, would it make as much sense to simply reopen the class in other files and define more methods in there?
In a command line application I am working on, I have a few classes that do various functions, but I have a top level class that provides an API for the overall application - what I found is that class is becoming bogged down with a lot of methods that really hand off work to other class, and is like the glues that holds the pieces of the application together. I guess I am wondering if it would make sense for me to split out some of the related methods into modules, or re-open the class in different code files? Or is there something else I am not thinking of?
I've created quite a few modules that I didn't intend to be highly reusable. It makes it easier to test a group of related methods in isolation, and classes are more readable if they're only a few hundred lines long rather than thousands. As always, there's a balance to be struck.
I've also created modules that expect the including class to define instance methods so that methods defined on the module can use them. I wouldn't say it's terribly elegant, but it's feasible if you're only sharing code between a couple of classes and you document it well. You could also raise an exception if the class doesn't define the methods you want:
module Aggregator
def aggregate
unless respond_to?(:collection)
raise Exception.new("Classes including #{self} must define #collection")
end
# ...
end
end
I'd be a lot more hesitant to depend on shared instance variables.
The biggest problem I see with re-opening classes is simply managing your source code. Would you end up with multiple copies of aggregator.rb in different directories? Is the load order of those files determinate, and does that affect overriding or calling methods in the class? At least with modules, the include order is explicit in the source.
Update: In a comment, Stephen asked about testing a module that's meant to be included in a class.
RSpec offers shared_examples as a convenient way to test shared behavior. You can define the module's behaviors in a shared context, and then declare that each of the including classes should also exhibit that behavior:
# spec/shared_examples/aggregator_examples.rb
shared_examples_for 'Aggregator' do
describe 'when aggregating records' do
it 'should accumulate values' do
# ...
end
end
end
# spec/models/model_spec.rb
describe Model
it_should_behave_like 'Aggregator'
end
Even if you aren't using RSpec, you can still create a simple stub class that includes your module and then write the tests against instances of that class:
# test/unit/aggregator_test.rb
class AggregatorTester
attr_accessor :collection
def initialize(collection)
self.collection = collection
end
include Aggregator
end
def test_aggregation
assert_equal 6, AggregatorTester.new([1, 2, 3]).aggregate
end
I have an external file: path_to_external_file.rb with some class definition:
class A
some_definitions
end
And I want to load that within module B so that the class A defined above can be referred to as B::A. I tried:
class B
load('path_to_external_file.rb')
end
but A is defined in the main environment, not in B:
A #=> A
B.constants # => []
How can I load external files within some class/module?
Edit
Should I read the external files as strings, and evaluate them within Class.new{...}, and include that class within B?
You cannot. At least using load or require, the Ruby files will always be evaluated in a top context.
You can work around that problem in two ways:
Define class B::A directly (but you are probably trying to avoid that)
Use eval(File.read("path_to_external_file.rb")) within your B class
Edit: Maybe, this library is interesting for you: https://github.com/dreamcat4/script/blob/master/intro.txt
Generally, it's a bad idea to define a class as "class A" but then "magically" make it contained by module B. If you want to refer to class A as B::A, you should define it using either:
module B
class A
# contents
end
end
or:
class B::A
# contents
end
Otherwise anyone who reads your code will be confused. In this case, you don't gain anything in clarity, brevity, or convenience by using "tricks", so straightforward code is better. There is a lesson here: the metaprogramming features of Ruby are great, but there is no need to use them gratuitously. Only use them when you really gain something from doing so. Otherwise you just make your code hard to understand.
BUT, having read your comment, it looks like there is really a good reason to do something like this in your case. I suggest that the following solution would be even better than what you are envisioning:
m = Module.new
m.module_eval("class C; end")
m.constants
=> [:C]
m.const_get(:C)
=> #<Module:0xfd0da0>::C
You see? If you want a "guaranteed unique" namespace, you can use an anonymous module. You could store these modules in a hash or other data structure, and pull the classes out of them as needed. This solves the problem you mentioned, that the users of your app are going to be adding their own classes, and you don't want the names to collide.
so I have an ruby object that i need to create as a pdf and excel row and cvs row
so far I've created a new class with a method to take in the object and do the necessary stuff to produce the pdf , excel , csv
I've been reading Agile Software Development, Principles, Patterns, and Practices and it mentioned the extension method so i was going to do but since this is ruby should i just be reopening the class in the another file and added the methods on there to separate them from the main class
so
file ruby_model.rb
class RubyModel < ActiveRecord::Base
end
then do
ruby_model_pdf.rb
class RubyModel
def to_pdf
end
end
ruby_model_cvs.rb
class RubyModel
def to_csv
end
end
or should i go with with the object extension pattern?
Cheers
You should put your methods in a module and include the module in the class. This way is preferable because it's easier to see where the methods came from (in a backtrace, for example), and it's easier to reuse the methods if it turns out that they can be used in other classes too.
For example:
module Conversions
def to_pdf
end
def to_csv
end
end
class RubyModel
include Conversions
end
It might also be a good idea to put to_pdf and to_csv in different modules, unless it's the case that if you want to mix in one you always want to mix in the other.
This all assumes that the methods don't belong in the class itself, but judging from the names they don't.
If the language feature works fine, then keep it simple and use it.
Design patterns are documented workarounds for cases where the language is not expressive enough. A Ruby example would be Iterator, which is made redundant by blocks and Enumerable.
In Ruby a class named Foo would be defined with class Foo, used via require 'foo' and would live in $:[0]/foo.rb or something like that.
But what about Foo::Bar? Would it be called with require 'foo/bar'? Would it live in $:[0]/foo/bar.rb? And how would it be defined?
I am very used to Perl, where for personal projects I would make nested classes like Project::User, Project::Text::Index, Project::Text::Search, etc. I would then make a file like Project/Text/Index.pm, which would start with package Project::Text::Index, and be called via use Project::Text::Index;.
Now I'm starting a project in Ruby and have no idea how to do this. For some reason none of the Ruby books or docs I've read mention perl-style hierarchical class naming. When they mention inheritance it's usually via a trivial made up example like class Foo < Bar which doesn't really help me. Yet I figure it must be possible to do what I'm attempting because Rails (just to take one example) has classes like ActionView::Helpers::ActiveModelFormBuilder.
You're combining a couple of concepts here which aren't really related, namely the load path, inheritance, and the scope resolution operator.
When requiring (or loading) files the argument to the require keyword is simply taken as a file path and appended to the load search path (the .rb extension is optional for require). Inheritance and nesting don't come into play here and any file can define anything it wants, e.g.:
require 'foo' # Looks for "foo.rb" in each of $:
require 'foo/bar' # Looks for "foo/bar.rb" in each of $:
Nested classes (and modules, variables, etc) are defined as expected but resolved with the scope resolution operator, e.g.:
class Foo
def foo; 'foo'; end
class Bar
def bar; 'bar'; end
end
end
Foo.new.foo # => "foo"
Foo::Bar.new.bar # => "bar"
Note that class nesting and inheritance are irrelevant to the location of the file from which they are loaded. There don't seem to be any explicit conventions for class/module structuring, so you're free to do what works for you. The Ruby Language page of the Programming Ruby book might be helpful too.
You need modules. If you want a class identified by Funthing::Text::Index in Funthing/Text/Index.rb and be required with require 'Funthing/Text/Index', you do this:
# file Funthing/Text/Index.rb
module Funthing
module Text
class Index
def do_the_twist()
puts "Let's twist again!"
end
end
end
end
Then use it like this:
require "Funthing/Text/Index"
c = Funthing::Text::Index.new
Note: the file hierarchy is not required, but (in my opinion) is best practice.
I have a Date class which I would like to use to overwrite Ruby's Date class. However, whenever I do a require 'Date' in my other files, it includes Ruby's Date class and not my own.
I thought that putting it in a module would work well, so I did so within the Date.rb file:
module myModule
class Date
#...
end
end
However I still can't figure out how to make my other classes include THIS Date class and not the built-in class. How can I achieve this?
All help is appreciated and thanks in advance!
Adam,
Your best bet is to simply follow some conventions:
Always name your filenames lower case (date.rb not Date.rb)
Put your files in a specific directory inside your library (lib is a good candidate)
Don't name your files the same thing as built in Ruby classes (call it my_date.rb or something) or if your class/module is name-spaced inside a module, put it in a folder of the module name (lib/my_module/date.rb).
This removes any ambiguity in which file you are trying to load. If you absolutely must keep it named date.rb, then load it with the full path by doing something like: File.join(File.dirname(__FILE__), "date.rb").
For debugging purposes you can look at the following special variables to see what's being loaded instead of your file:
$: will show the load path (i.e. every directory it looks in to find files to require. You will note that the current directory (.) is last. This is why your file isn't loaded -- it looks in the system path first. You can always move your current directory to the front of the load path as a solution by doing $:.unshift(File.dirname(__FILE__)), but I'd try one of the above approaches before resorting to this
$" shows every file that has been required into your current environment so far.
require 'path/to/Date.rb'
class MyClass
include MyModule::Date
end
First, you need to require the correct file. Often the right thing happens when you do require 'date' and it's resolved to a file based on your $LOAD_PATH. You can be more specific by putting your date.rb in a directory so you can require 'my_module/date' or just use a relative path like ./date
You can then specify the module hierarchy when referring to this class:
::MyModule::Date
Or you can include it wherever you prefer to call this Date over the standard one without specifying:
class Event
include MyModule
def initialize
#date = Date.today # refers to MyModule::Date
end
end