Where to learn ruby DSL? - ruby

Vagrant.configure(2) do |config|
config.vm.define "chefnode" do |chefnode|
chefnode.vm.box = "geerlingguy/ubuntu1604"
chefnode.vm.hostname = 'cnode'
chefnode.vm.network "public_network"
end
config.vm.define "chefserver" do |chefserver|
chefserver.vm.box = "geerlingguy/ubuntu1604"
chefserver.vm.hostname = 'cserver'
chefserver.vm.network "public_network"
end
end
I am badly struggling on the above (vagrant dsl) code. Is this Ruby DSL or plain Ruby or something else? Why is there an "=" sign for vm.box and vm.hostname, but not for vm.network??

first of all it's ruby, which as a language can be very handy when you want to create your own DSL. All DSLs are built using base ruby concepts like blocks (do ... end) - no magic here.
In your case we may say it's DSL created by Vagrant owners.
Why do they once use = and an another time not? In this specific case I assume it's caused by fact you have one argument which is a String and is required, and later on yo may pass a hash with different setup options, like in example from documentation.
Vagrant.configure("2") do |config|
config.vm.network "forwarded_port", guest: 80, host: 8080
end
If they wanted to use = they would have to enforce you to pass a Hash in which name key would be required and the rest would be optional, I mean something like this:
config.vm.network = { name: "default" }

This is standard Ruby, no DSL or metaprogramming magic here.
Ruby is unusual in this regard, but the following are both calling methods on the object returned by chefserver.vm:
chefserver.vm.hostname = 'chefserver' # calls method 'hostname='
chefserver.vm.network "public_network" # calls method 'network'
Ruby does not require the use of parentheses for a method call (although sometimes they are necessary to clarify to the interpreter what you mean).
I don't know Chef, so I can't say why the network call is not network=, except that in Ruby calling a method whose name ends with '=' with more than 1 parameter does not work. And although only 1 parameter to network is specified here, in Ruby there can be optional arguments. The method may have been defined something like this:
def network(name, something = 'foo')
# ...
end
...so that the method can be called with either 1 or 2 arguments.

Related

How do I share object from main file to supporting file in ruby?

I have something similar.
# MAIN.RB
require 'sockets'
require_relative 'replies.rb'
hostname = 'localhost'
port = 6500
s = TCPSocket.open(hostname, port)
$connected = 0
while line = s.gets # Read lines from the socket
#DO A BUNCH OF STUFF
if line == "Hi"
reply line
end
end
s.close
Then I have the reply function in a secondary file.
# REPLIES.RB
def reply(input)
if input == "Hi"
s.write("Hello my friend.\n"
end
end
However calling on the object s from the second file does not seem to work. How would I go about making this work. I'm new to Ruby. I've searched google for the answer, but the only results I have found is with sharing variables across files. I could always do a return "Hello my friend.\n", but I rather be able to write to the socket object directly from the function in REPLIES.rb
Remember that variables are strictly local unless you expressly pass them in. This means s only exists in the main context. You can fix this by passing it in:
reply(s, line)
And on the receiving side:
def reply(s, input)
# ...
end
I'd strongly encourage you to try and indent things consistently here, this code is really out of sorts, and avoid using global variables like $connected. Using a simple self-contained class you could clean up this code considerably.
Also, don't add .rb extensions when calling require. It's implied.

Vagrantfile ruby syntax explanation

I'm confused with the ruby syntax used to configure Vagrant. Especially with this construct. Is this an assignment, a method call, or something else? Is it pure ruby or vagrant specific dialect?
config.vm.network "forwarded_port", guest: 3000, host: 3000
And this one. Is the "ansible" an assignment or an argument, and where |ansible| comes from?
config.vm.provision "ansible" do |ansible|
ansible.playbook = "provisioners/docker.yml"
end
Where I can find more information about those specific expressions?
Those are DSLs, Ruby is a very good language for writing DSL, take a look a this other question
Although those are DSLs, you can throw vanilla Ruby code outside those blocks, and probably inside as well as long as it gets evaluated.
The Vagrantfile is written in standard Ruby syntax.
Here is an example Vagrantfile
Vagrant.configure("2") do |config|
# this is an evaluation statement
# .box is an string attribute
config.vm.box = "debian/stretch64"
# this is a method call
# .synced_folder is a method that takes two positional arguments ('synced_folder', '/vagrant'),
# followed by some keyword arguments (disabled: true)
config.vm.synced_folder 'synced_folder', '/vagrant', disabled: true
# this is a method call, followed by a "do ... end" block
# .provider is a method that takes one positional argument (:libvirt)
config.vm.provider :libvirt do |node|
# these are two evaluation statements
node.cpus = 4
node.memory = 4096
end
end
From the official documents,
you can see the variable "string" in "config.vm.box (string)",
but not following the methods config-vm-provider and config-vm-synced_folder.
I was also confused about the Vagrantfile syntax, even after reading the offical documents.
I think it's because Ruby looks very different to me compared to other languages I used before.

Naming and variables of config.vm.define

In the tutorial
https://docs.vagrantup.com/v2/multi-machine/
there are a few examples of code such as
config.vm.define :testing do |test|
config.vm.define "web" do |web|
In some of these examples, the string after define is the same as after do (web, web) , in some it is not (testing, test). Why?
Also, why use quotes with "web" but colon with :testing ?
Its more ruby language than vagrant, but basically config.vm.define is a method which takes one parameter, then there is a ruby block statement and within this block the method parameter has a specific name which is defined between the |
Also, why use quotes with "web" but colon with :testing ?
As a ruby novice, I would say it is the same - the :x is called symbols and you can read some differences about using one or the other

How can I create a custom :host_role fact from the hostname?

I'm looking to create a role based on host name prefix and I'm running into some problems. Ruby is new to me and although I've done extensive searching for a solution, I'm still confused.
Host names look like this:
work-server-01
home-server-01
Here's what I've written:
require 'facter'
Facter.add('host_role') do
setcode do
hostname_array = Facter.value(:hostname).split('-')
first_in_array = hostname_array.first
first_in_array.each do |x|
if x =~ /^(home|work)/
role = '"#{x}" server'
end
role
end
end
I'd like to use variable interpolation within my role assignment, but I feel like using a case statement along with 'when' is incorrect. Please keep in mind that I'm new to Ruby.
Would anybody have any ideas on how I might achieve my goal?
Pattern-Matching the Hostname Fact
The following is a relatively DRY refactoring of your code:
require 'facter'
Facter.add :host_role do
setcode do
location = case Facter.value(:hostname)
when /home/ then $&
when /work/ then $&
else 'unknown'
end
'%s server' % location
end
end
Mostly, it just looks for a regex match, and assigns the value of the match to location which is then returned as part of a formatted string.
On my system the hostname doesn't match either "home" or "work", so I correctly get:
Facter.value :host_role
#=> "unknown server"

How to define a simple global variable in an rspec test that can be accesed by helper functions

I cant figure out how to use a simple global variable in an rspec test. It seems like such a trivial feature but after much goggleing I havent been able to find a solution.
I want a variable that can be accessed/changed throughout the main spec file and from functions in helper spec files.
Here is what I have so far:
require_relative 'spec_helper.rb'
require_relative 'helpers.rb'
let(:concept0) { '' }
describe 'ICE Testing' do
describe 'step1' do
it "Populates suggestions correctly" do
concept0 = "tg"
selectConcept() #in helper file. Sets concept0 to "First Concept"
puts concept0 #echos tg?? Should echo "First Concept"
end
end
.
#helpers.rb
def selectConcept
concept0 = "First Concept"
end
Can someone point out what I am missing or if using "let" is totally the wrong method?
Consider using a global before hook with an instance variable: http://www.rubydoc.info/github/rspec/rspec-core/RSpec/Core/Configuration
In your spec_helper.rb file:
RSpec.configure do |config|
config.before(:example) { #concept0 = 'value' }
end
Then #concept0 will be set in your examples (my_example_spec.rb):
RSpec.describe MyExample do
it { expect(#concept0).to eql('value') } # This code will pass
end
It turns out the easiest way is to use a $ sign to indicate a global variable.
See Preserve variable in cucumber?
This is an old thread, but i had this question today. I just needed to define a long string to stub out a command that is in multiple files as:
# in each spec file that needed it
let(:date_check) do
<<~PWSH.strip
# lots of powershell code
PWSH
end
# in any context in that file (or a shared context)
before(:each) do
stub_command(date_check).and_return(false)
end
Searched, Stack Overflow, etc, landed on this: Note the usage of the variable doesn't change at all! (Assumes all specs require 'spec_helper')
# in spec_helper.rb
def date_check
<<~PWSH.strip
# lots of powershell code
PWSH
end
# in any context in any spec file
before(:each) do
stub_command(date_check).and_return(false)
end
I suggest you define the variable in the helper file, where it can be used by other helper code, and can be accessed from your tests.
For my project, I wanted to keep all the setup stuff in spec_helper.rb, and use those settings, plus any custom variables and methods in the tests. The following, modified from the RSpec-core 3.10 docs, is not Rails-specific.
Create a new setting for RSpec.configure called my_variable, and give it a value, like this:
# spec/spec_helper.rb
RSpec.configure do |config|
config.add_setting :my_variable
config.my_variable = "Value of my_variable"
end
Access settings as a new read-only property in RSpec.configuration from your test:
# spec/my_spec.rb
RSpec.describe(MyModule) do
it "creates an instance of something" do
my_instance = MyModule::MyClass.new(RSpec.configuration.my_variable)
end
end

Resources