Thor - inject into file at end - ruby

I am working on a rails engine and I am trying to write a generator that will put this line
do_stuff (foo)
as the last statement in config/routes.rb, without breaking the file syntax.
Specifically, if my config/routes.rb looks like this currently
Rails.application.routes.draw do
blah
more blah
end
After running the generator I would like the config/routes.rb to look like this
Rails.application.routes.draw do
blah
more blah
do_stuff (foo) # injected line
end
I looked at what ActiveAdmin does, but am unable to create a blanket last line rule. Any help is greatly appreciated.

i did not test that out, but i think from what you linked to in the ActiveAdmin generator it might work like this:
inject_into_file "config/routes.rb", " do_stuff(foo)\n", :before => /^end/
this should insert your code right before an end token that starts at the beginning of a line. this only works for properly formatted routes files though....

Related

Syntax for calling and how to write yaml file in ruby

Here is main.rb file (the driver of the project)
require_relative '../lib/logic'
So first of all, how to make a YAML file? Well, there are many tutorials out there to teach you the basics of a format, but a little shortcut is to just use the .to_yaml method on a Ruby object, and look at the output:
require 'yaml'
checks_to_run = [
"check_alphabetized_constants",
"check_bfr_return_emp_line"
]
puts checks_to_run.to_yaml
Which prints
---
- check_alphabetized_constants
- check_bfr_return_emp_line
--- always goes on the first line, and then you have a list of strings (quotations are optional) - simple enough.
You can write this to a file like so:
File.open("checks.yaml", "w") { |f| f.write checks_to_run.to_yaml }
You can of course write or edit the YAML file by hand as needed.
Now, to read the YAML file:
checks_to_run = YAML.load(File.read("checks.yaml"))
# => ["check_alphabetized_constants", "check_bfr_return_emp_line"]
From this point, you can loop through the checks and call the methods. There are multiple ways to do this, for example you could use send:
checks_to_run.each do |check_to_run|
check.send(check_to_run)
end
Or you could skip the metaprogramming and use something like if:
if checks_to_run.include?("check_alphabetized_constants")
check.check_alphabetized_constants
end
# repeat for the other checks as well

How do I require a file without defining it's constants on Object in Ruby?

I would like to require a file in Ruby without defining that file's constants on Object. Instead I would like to include them only in a module in the requiring file. For instance if I have a file foo.rb that looks like this:
module Foo
def self.hello_world
puts 'Hello, World.'
end
end
A representation of the result I hope to achieve looks something like this:
module Bar
require_relative './foo.rb'
end
Foo.hello_world
# NameError: uninitialized constant Foo
Bar::Foo.hello_world
# Hello, World.
It seems like the way things are required in Ruby, anything defined at the top level of another file will be defined as a constant on Object and thus globally available. I'm having a problem because I need something from a file that conflicts with a constant in the global namespace.
I recognize that this problem may be fundamental to Ruby, but is it possible there's some metaprogramming technique to overcome this issue?
It looks like the following snippet works for what I'm trying to do:
module Bar
class_eval File.open('./foo.rb').read
end
It may be that I'm still missing something though.
If you don't need to access the ancestor, what you're looking for is extend
module Foo
def hello_world
puts 'Hello, World.'
end
end
module Bar
extend Foo
end
Bar.hello_world

Access `expected` line from metadata

I want output line, which was failed during rspec comparasion inside example, but I don't know how to do it best.
For example I have test like this:
require 'rspec'
describe 'My behaviour' do
it 'should do something' do
test_string = 'test'
expect(test_string).to eq('failed_test')
end
after :each do |example|
puts example.metadata[:expect_line]
end
end
And I want to outputed line in after :each be
"expect(test_string).to eq('failed_test')"
I know, I have acces to example.metadata[:location] which return something like "./spec/test_spec.rb:4" and I can parse it and extract line, but is there already something like I need hided in whole example structure?
Update:
I just understand. that example.metadata[:location] return not failed line, but actually line in whitch it started, so it have no use for me :(
So question still exist - how to get failed line?
This information isn't hidden anywhere in the example structure that I'm aware of. RSpec's default output shows the line that failed:
Failures:
1) My Description should fail
Failure/Error: expect(1).to eq(2)
expected: 2
got: 1
If we look at how rspec itself gets this in rspec/core/formatters/exception_presenter.rb and rspec/core/formatters/snippet_extractor.rb, it appears that they go through the backtrace of the example exception to find the spec file and extract the line (similar to what you mentioned). If there was an easier way to pull that out of the example, I would think RSpec itself would use that :)

How can I "require" code from another .rb file like in PHP?

Coming to Ruby from a PHP background, I'm used to being able to use require, require_once, include, or include_once which all have a similar effect, but the key being they continue to process code in the same scope where the include / require command was invoked.
Example:
sub.php
<?php
echo $foo;
main.php
<?php
$foo = 1234;
include('sub.php'); // outputs '1234'
When I first started using Ruby I tried to include / require / require_relative / load other .rb files, and after becoming a little frustrated with not having it work how I would expect it to I decided that there were better ways to go about breaking up large files and that Ruby didn't need to behave in the same way PHP did.
However, occasionally I feel that for testing purposes it would be nice to to load code from another .rb file in the way PHP does - in the same scope with access to all the same variables - without having to use class / instance variables or constants. Is this possible? Maybe somehow using a proc / binding / or eval command?
Again, I'm not advocating that this should be used during development - but I am curious if it is possible - and if so, how?
Yes, this is possible, although certainly not something I'd recommend doing. This works:
includer.rb:
puts var
include.rb:
var = "Hello!"
eval(File.read("include.rb"), binding)
Running this (Ruby 2.2.1, Ruby 1.9.3) will print Hello!. It works simply: eval takes an optional binding with which to evaluate the code it is passed, and Kernel#binding returns the current binding.
To have code run in same binding, you could simply eval the file contents as follows:
example.rb
class Example
def self.called_by_include
"value for bar"
end
def foo
puts "Called foo"
end
eval( File.read( 'included.rb' ) )
end
Example.new.bar
included.rb
BAR_CONSTANT = called_by_include
def bar
puts BAR_CONSTANT
end
Running ruby example.rb produces output
value for bar
The important thing is the eval( File.read( 'included.rb' ) ) code, which if you really wanted you could define as a class method on Object, to allow arbitrary source to be included with a convenience function*. The use of constants, class variables etc just shows influences working in both directions between the two pieces of source code.
It would be bad practice to use this in any production code. Ruby gives you much better tools for meta-programming, such as ability to use mix-ins, re-open classes, define methods from blocks etc.
* Something like this
class Object
def self.include_source filename
eval( File.read( filename ) )
end
end
And the line in example.rb would become just
include_source 'included.rb'
Again I have to repeat this is not such a great idea . . .
To import external .rb file in your code, I'm not sure but I think it have to be a gem.
Use require followed by the name of the gem you want to import.
Example
require 'foobar'
# do some stuff
Or you can use load to import entire rb file
load 'foobar.rb'
# do some stuff
Good luck and sorry for my english

File.read empty for a non empty file when testing with rspec

New to rubby and rspec i am trying to test a class that opens and write to a file.
The class name is SimpleLogger
Here is the spec that generates an error:
describe SimpleLogger do
...
context 'when using a file' do
require 'fakefs/spec_helpers'
before(:all) do
#path = 'my_file'
logger = SimpleLogger.new #path
logger.write "Hello, world!"
logger.close
end
...
it 'we expect the file to have a valid content' do
expect(File.read(#path)).to eq "Hello, world!\n"
end
end
end
The error generated is:
Failure/Error: expect(File.read(#path)).to eq "Hello, world!\n"
expected: "Hello, world!\n"
got: ""
(compared using ==)
Diff:
## -1,2 +1 ##
-Hello, world!
The file exists on my file system, and when I'm testing a simple puts Find.read("my_file") on an independant ruby file i've got the expected result.
I've tested and have the same issue without the fakefs gem
Why is it when run in a spec it doesn't work?
And beside that i fail to understand the advantage of fakefs, as it creates the file juste the same. So why fakefs is used?
And as it creates the file should i erase it within the spec?
Thanks in advance ;)
From the documentation - it seems that you need to include the helpers to activate the FakeFS:
FakeFS::SpecHelpers provides a simple macro for RSpec example groups to turn FakeFS on and off.
To use it simply require 'fakefs/spec_helpers', then include FakeFS::SpecHelpers into any
example groups that you wish to use FakeFS in. For example:
require 'fakefs/spec_helpers'
describe "Some specs that deal with files" do
include FakeFS::SpecHelpers
...
end
By default, including FakeFS::SpecHelpers will run for each example inside a describe block.
If you want to turn on FakeFS one time only for all your examples, you will need to
include FakeFS::SpecHelpers::All.
Alternatively, you can include FakeFS::SpecHelpers in all your example groups using RSpec's
configuration block in your spec helper:
require 'fakefs/spec_helpers'
Spec::Runner.configure do |config|
config.include FakeFS::SpecHelpers
end
If you do the above then use_fakefs will be available in all of your example groups.
You will also need to use before(:each) instead of before(:all) - like many unit test helpers, FakeFS adheres to unit-test isolation principles, in which side-effects of one test should not affect another's. That is why after every test, the gem 'resets' the state of its container, and clears all files from it.

Resources