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

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.

Related

How to Test/Debug Jekyll Plugins?

For a blog, I put together an inline tag that pulls header information from a specified webpage. It works, but I need to add caching, so that I'm not making redundant network calls.
I'd like a tighter debugging cycle than restarting the server and waiting for a rebuild, but running the plugin as Ruby code reports uninitialized constant Liquid (NameError). That makes sense, since it isn't required, and wouldn't run, since the plugin is just a class definition.
So, I tried creating some scaffolding to run the code, or what I thought would be scaffolding, anyway.
require 'liquid'
require_relative '../_plugins/header.rb'
ht = HeaderInlineTag.new
ht.initialize 'header', 'some path'
puts ht.render()
This produces...
_test/header.rb:4:in `<main>': private method `new' called for HeaderInlineTag:Class (NoMethodError)
Considering the possibility that initialize() might be run to create objects, I combined the first two lines of code, but that didn't work, either. Same error, different function name. The plugin doesn't mark anything as private, and declaring the methods public doesn't change anything.
What more is needed to test the plugin without carrying around the entire blog?
The solution was beyond my Ruby knowledge, but mostly straightforward, once I connected the pieces of information floating around.
First, this existing answer is about a specific issue dealing with Rails, but incidentally shows how to deal with private new methods: Call them through send, as in HeaderInlineTag.send :new.
Then, this indirect call to .new() now (of course) calls .initialize(), meaning that it needs the three parameters that are required by any plugin. Two parameters are for the testing itself, so they're easy. The third is a parse context. Documentation on writing Jekyll plugins is never clear on what the parse context actually is, since it gets sent automatically as part of the build process. However, some research and testing turns up Liquid::ParseContext as the culprit.
Finally, .render() also takes the ParseContext value.
Therefore, the the test scaffold should look something like this.
require 'liquid'
require_relative '../_plugins/header.rb'
context = Liquid::ParseContext.new
ht = HeaderInlineTag.send :new, 'header', 'some path...', context
puts ht.render context
I can call this from my blog's root folder with ruby _test/header.rb, and it prints the plugin's output. I can now update this script to pull the parameters from the command line or a CSV file, depending on the tests required.

It's correct to make a run method inside the class to start the flow of the 'program'?

I wrote this scraper script to extract the job list from a website. And then to in order to practice I decide to try to transform this script into a class object.
The correct approach is to just call the methods you need as it is below.
teste = InfoJobs.new
teste.build_url
teste.get_page_values
teste.scraping
teste.writing
but I want to know if is ok to have a run method inside of my class and use self. to make the flow of the scrape program.
def run
self.build_url
self.parsing(#url)
self.get_page_values
self.scraping
self.writing
end
teste.run
If you're asking "should I create an abstraction layer around the numerous steps required to perform the operation so that the caller doesn't need to care about the particulars" then the answer is that's fine.
I'd prefer to write code that says scraper.run than five lines of confusing boilerplate which doesn't afford me any more control than the equivalent run method does.

Is there a way to combine page object gem and javascript calls

Im using the page object gem and selenium,
when filling in a sign up form, the form fills in correctly, but when clicking apply it errors saying the fields are required even though they are filled in.
this seems to be caused because the page object/selenium method isn't firing the javascript change method which is needed for the application to know the field has been filled in
this can be fixed by using code such as
on(SettingsPage).payment_method_account_number = number
#browser.execute_script("$('input[name=account_number]').change()")
but this is obviously not ideal and breaks the whole point of using page object in the first place by having to declare the fields name attribute again
is there a way better way to solve this problem than what i have shown?
To avoid duplicating an element definition within the page object as well as the execute_script script, you can pass the page object element to the script.
The underlying Selenium-WebDriver (and therefore the Page-Object gem) supports an arguments array within the script being executed. This arguments array basically takes a Selenium-WebDriver elements and converts them to something usable by the script. The Page-Object execute_script method handles the conversion of elements to the right type, so you simply need to:
Declare a script that uses the arguments array
Pass in a PageObject::Element
For example, let us assume your page object has used the accessor:
text_field(:payment_method_account_number, :name => 'account_number')
Therefore, the page object will have a payment_method_account_number_element method that returns the PageObject::Element for this text field.
Within the script you want to execute, you can replace how you locate the element with the arguments array and pass in the PageObject::Element to the execute_script method:
execute_script("$(arguments[0]).change();", payment_method_account_number_element)
Then you can re-write the call to on as:
on(SettingsPage) do |page|
page.payment_method_account_number = number
page.execute_script("$(arguments[0]).change();", page.payment_method_account_number_element)
end
(Or, as Dane pointed out, put this into a method in the page object.)
I have had a similar problem but the event was "onblur" instead of "onchange". I would imagine the on change would fire, but if it doesn't you could use an approach similar to mine. I ended up creating a widget that redefined the "#{name}=" method to also call the event on the spot. It's a little bit more complicated, but it centralizes all the magic to one class and keeps the code brief.

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)

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

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

Resources