Ruby Style: should initialize take a file with data or just the raw data as parameters - ruby

I was curious if anyone had insight on what is the best way for an object to load data from a file in Ruby. Is there a convention? There are two ways I can think of accomplishing this:
Have the initialize method accept a path or file and parse the data within the initialize method, setting the object variables as well.
Have the main "runner" code open the file and parse it, then pass the correct arguments to your constructor.
I am also aware that I could support both methods through an options hash or *args and looking at its size, but I do not have any need to implement both.

I would use the second option combined with providing the path info as an argument to the main code. This makes it more portable and keeps the object de-coupled from the source of the data

Related

Modify an object before marshaling it in Ruby

I have an object containing sensitive data that I want to marshal (using Marshal) without the sensitive data.
I'd like to be able to say:
def _dump(*args)
# Clean-up sensitive data
super
end
but this produces a 'no superclass method' error. Is there a way I can make my object behave the way I want in response to Marshal.dump, while using the default implementation?
I want Marshal.dump(my_obj) to work out-of-the-box without requiring the API consumer to remember to call a different method.
It may be that there is no superclass method for _dump. If it's defined on your object it's called. If not, the default handler is used.
You probably want to clone your object and remove the sensitive fields, returning that as a Hash inside your _dump function, then undo that within the _load method.
You can also read the documentation on Marshal where it describes the recommended methods.

Passing command line arguments to classes

When developing large scale command line applications that are composed of multiple classes, which need to use options passed from the command line, how do you construct the code such that you can use those options?
I write code like this:
class DatabaseHandler
def initialize(cmd_options = {})
#cmd_options = cmd_options
end
def some_method
puts #cmd_options[:cmd_parameter]
end
end
which, seems tedious and unnecessary to me. What is the Ruby best practice for using command line option parameters in your project's classes? Help appreciated.
Ruby classes are simply that: classes. Good OOD applies, even if it's a command line application. If you need custom behaviour, use dependency injection or configure it using arguments. You may share the command line options using global variables, but as always, that comes at a price: shadowing, increasing complexity to understand the collaborators of a class, difficulty finding the source of a data, etc.
I'd suggest using factory methods to parse the input and return the correct configuration to be passed to a class. If you want nice examples of dependency injection, watch some Sandi Metz talks, she really knows her stuff.
Command-line options are no different from any other kind of configuration; configuration is configuration, no matter where it came from. So, you deal with them the same way you deal with other configuration, e.g. using
a global singleton
Dependency Injection (which is what you are doing in your example)
the Reader Monad
…
For example, for request parameters (which is probably the closest analog to command-line arguments in a Web context), Rails uses a global method named params which returns a Hash-like object mapping parameter names to arguments. So, that would be an example of a global singleton.

Ruby: How to get method content dynamically and write it to file?

I'm working on transforming legacy code to a new one in a new project.
There are more than 100 of similar codes and I have to transform them to a slightly different new format.
Basically, get a particular method from the legacy application, rename it, modify the content of the method to fit the new format, and put that method in a class for the new project.
Since there are more than 100 of them, I want to do it programmatically, instead of manually copying and pasting and modifying.
Is there a way to get the source code of a method as a string dynamically?
It must be only for a specific method, not the entire content of the class or file.
After that is done, I think I can just do gsub, or maybe use AST (Abstract Syntax Tree) to pass to Ruby2Ruby.
So I need more than the answers for the question How can I get source code of a methods dynamically and also which file is this method locate in?.
Any help will be greatly appreciated.
After further investigation, I resorted to use live_ast gem to convert the method object to Abstract Syntax Tree and generate the code for the method from that Abstract Syntax Tree (it's using Ruby2Ruby underneath).
Actually, live_ast provides a convenient method to_ruby to do the both steps.
It's working very well.
e.g.
require 'live_ast'
require 'live_ast/to_ruby'
SomeClassWithMethod.instance_method(:method_name).to_ruby
You could use source_location to find the beginning of the method you're looking for, then parse the file from that point until the end of the method. You could examine each line of the file starting from the start of the method, incrementing a counter when you find the start of a block and decrementing it when you reach the end of a block, until the counter reaches 0.

How can I update an already instantiated Ruby object with YAML?

Basically, I have an instance of a Ruby object already but want to update whatever instance variables I can from yaml. There is a to_yaml function that will dump my object to yaml. I'm looking for something in the reverse. For example, my_obj.from_yaml(yaml_stuff) and have it update instance variables from the yaml passed in.
Would I need to, in my from_yaml function, use YAML::load and copy each instance variable? Is there a function I can use to quickly copy those variables without much typing if that is the case?
Does Ruby's yaml library have something already where I can pass it the object and the yaml and it'll just do what I want it to do?
Editing for clarity
This is a simple object that will store and load very simple yaml compatible types such as strings and integers.
What I ended up doing
Although I answered this question I wanted to add what I ended up doing, my Object monkey patch
class Object
def from_yaml(yml)
if (yml.nil?)
return
end
yml.instance_variables.each do |iv|
if (self.instance_variable_defined?(iv))
self.instance_variable_set(iv, yml.instance_variable_get(iv))
end
end
end
end
Your question is not clear enough. Which class are you talking about? What kind of YAML documents? You can't have everything serialized to and from YAML.
Let's assume that your object just has a set of instance variables of simple, YAML-compatible types, such as strings, numbers and symbols.
In that case, you can generally, write from_yaml method, which would load YAML file into a hash of key->value pairs, iterate through it and update every instance variable named key with value. Does that seem useful, and if it does, do you need help writing such method?
Edit:
There is no need for you to keep your object state in a hash - you can still use ivars and attr_accessors - just open up a new module (say YamlUpdateable), implement a from_yaml method which would update your ivars from a hash deserialized from YAML, and include the module in whichever class you want to deserialize from YAML.
As far as I know, there's nothing like that included with the YAML library itself; it's mostly meant for dumping and reading data, not keeping it up-to-date in memory and on disk. If you're planning to keep data in memory and on disk synced with each other with minimal hassle, have you considered a data persistence library like ActiveRecord or Stone?
If you're still keen on using the YAML library, and assuming you don't have many different classes to persist, it might make sense to simply write a small "updater" method that updates an object of that class given a similar object. Or you could rework your application to make sure you can simply reload all the objects from the YAML without having to update them (i.e., dump the old objects and create new ones).
The other option is to use metaprogramming to read into an object's properties and update them accordingly, but that seems error-prone and dangerous.
What you are looking for is the merge command.
// fetch yaml file
yml = YAML.load_file("path/to/file.yml")
// merge variables
my_obj.merge(yml)

Unit Testing File I/O Methods

I am still relatively new to unit testing. I have written a class in Ruby that takes a file, searches through that file for a given Regex pattern, replaces it, then saves the changes back to the file. I want to be able to write unit tests for this method, but I don't know how I would go about doing that. Can somebody tell me how we unit test methods that deal with file i/o?
Check out this How do I unit-test saving file to the disk?
Basically the idea is the same, the FileSystem is a dependency for your class. So introduce a Role/interface that can be mocked in your unit-tests (such that you have no dependency while unit-testing); the methods in the role should be all the stuff you need from the FileSystem -
Read() # returns file_contents as a string or string[]
Write(file_contents) # same as above
Now you can use a mock to return a canned string array - your class can then process this array. In production code, the real implementation of the role would hit the filesystem to return the array of lines.
You could create a file with known contents, do your replacement method, and then validate the contents of the modified file with the desired result.
I'd suggest using temporary files http://ruby-doc.org/core/classes/Tempfile.html for each run, and have a good read at unit testing framework http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html
If you are passing a file object to your method; try creating a dummy file object, use some i/o data streams to add contents to the file object and pass it to the method being tested.
If you are just passing the contents of the object using some datastream, create a dummy datasream and pass it to the method.
You can also opt to have a dummy file and create a file object from that file path and pass it to your method being tested.

Resources