Ruby - calling a function for each included module - ruby

I'm working on an XML export for a Ruby project and I'm looking for an elegant way to implement it. The goal of this XML file is to export all data inside a project container and there is several models (about 20) sharing some common properties (for example a name and a description).
Currently, the XML export looks like an awful thing like that:
def export_project(p)
#xml project {
#xml.name p.name
#xml.description p.description
#xml.itemAs {
p.item_as.each {|item_a|export_itemA(item_a)
}
#xml.itemBs {
p.item_Bs.each {|item_B|export_itemB(item_b)
}
#xml.itemCs {
p.item_cs.each {|item_c|export_itemC(item_c)
}
}
end
def export_itemA(a)
#xml.itemA {
#xml.name a.name
}
end
def export_itemB(b)
#xml.itemB {
#xml.description b.description
}
end
def export_itemC(c)
#xml.itemC {
#xml.name c.name
#xml.description c.description
}
end
Which is pretty ugly (well, it's bearrable with 4 types, but the reality is 480 lines of mess ...)
What I'd like would be something like that (considered there is a magic mapping between a model and an exporter):
module Named
def export
#xml.name #context.name
end
end
module Described
def export
#xml.description #context.description
end
end
class ProjectExporter < ModelExporter
include Named
include Described
def export
#xml.project {
super
#xml.itemAs {
export_items(p.item_as)
}
#xml.itemBs {
export_items(p.item_Bs)
}
#xml.itemCs {
export_items(p.item_cs)
}
}
end
class ItemAExporter < ModelExporter
include Named
def export
#xml.itemA {
super
}
end
end
class ItemBExporter < ModelExporter
include Described
def export
#xml.itemB {
super
}
end
end
class ItemCExporter < ModelExporter
include Named
include Described
def export
#xml.itemC {
super
}
end
end
The problem with this method is that "super" will only call the export method of one of the module, not all of them.
I'm pretty suer the module and super approach is not the correct one, but I'm unable to find something more suitable. Any idea ?
Cheers and thanks,
Vincent

Related

Jenkins | parallel | problem to run class instances

I'm trying to run stages from 2 class instances with parallel but i'm getting this error: "roovy.lang.MissingFieldException: No such field" from one of them, but if i'm running one of them it ok.
the groovy classes are in src folder and i'm using them with library and creating an instance for each of them.
this is my parallel code from the jenkins file:
def parallelStagesMap = pipelineDailyStages.collectEntries {
def name = it.getSetupname()
echo "name: " + name
["${name}" : it.generateStage(name)]
}
these are the classes:
package stages
class DSmall extends DStages implements Serializable{
def local_mx
def local_g
def local_agent
DamSmall(environment, local_setupname, local_rp_launch_id, local_ssbuild, local_catagry, local_runners, local_artifactstodownload, local_resourcepool, local_mngnetwork, local_datastore, local_vmfolder, local_dcap_deploy, local_remotedebugport, local_m, local_g, local_agentoracle){
super(environment, local_setupname, local_rp_launch_id, local_ssbuild, local_catagry, local_runners, local_artifactstodownload, local_resourcepool, local_mngnetwork, local_datastore, local_vmfolder, local_deploy, local_remotedebugport)
this.local_m = local_m
this.local_g = local_g
this.local_agent = local_agent
}
def generateStage(a){
return {
this.environment.stage("stage: ${a}") {
this.environment.echo "This is da."
}
}
}
}
class DaSmall extends DStages implements Serializable{
def local_m
def local_agent
def local_oracletemplate
def local_oracle_start_db
DasSmall(environment, local_setupname, local_rp_launch_id, local_ssbuild, local_catagry, local_runners, local_artifactstodownload, local_resourcepool, local_mngnetwork, local_datastore, local_vmfolder, local_dcap_deploy, local_remotedebugport, local_mxs, local_agent, local_oracletemplate, local_oracle_start_db){
super(environment, local_setupname, local_rp_launch_id, local_ssbuild, local_catagry, local_runners, local_artifactstodownload, local_resourcepool, local_mngnetwork, local_datastore, local_vmfolder, local_deploy, local_remotedebugport)
this.local_mxs = local_mxs
this.local_agentoracle = local_agentoracle
this.local_oracletemplate = local_oracletemplate
this.local_oracle_start_db = local_oracle_start_db
}
def generateStage(a){
return {
this.environment.stage("stage: ${a}") {
this.environment.echo "This is da."
}
}
}
}
Finally I found a solution, I created the class instances from another groovy file that sits in vars folder(not from the jenkins file)

Unit testing a method in which I am doing a dependency injection

I have a terminal app with two classes: Todo and TodoApp. The method below lives in TodoApp, I would like to unit test this method and keep it isolated in my test. Since I am doing a dependency injection within the method, how could I mock that? (#todos is an empty array in TodoApp initialize)
def add(task, day, todo = Todo)
#todos.push(todo.new(task, day))
return "#{task} was added to your todos"
end
Thanks in advance for your help.
I imagine your code looks like this.
class TodoApp
def initialize
#todos = []
end
def add(task, day, todo = Todo)
#todos.push(todo.new(task, day))
return "#{task} was added to your todos"
end
end
This is NOT injecting the empty array into the TodoApp. And, therefore you are going to have difficulty accessing it from the tests.
But, if your TodoApp object looked like this:
class TodoApp
def initialize(todos = [])
#todos = todos
end
def add(task, day, todo = Todo)
#todos.push(todo.new(task, day))
return "#{task} was added to your todos"
end
end
Now you are injecting something into TodoApp that can be mocked, or even just evaluated:
describe TodoApp do
subject(:app) { described_class.new(todos) }
let(:todos) { [] }
describe '#add' do
subject(:add) { app.add(task, day) }
let(:task) { 'task' }
let(:day) { 'day' }
it 'pushes the item on the list of todos' do
expect { add }.to change { todos.length }.by(1)
end
end
end

How to define a private define_singleton_method without using `send`?

I want to define a singleton method, but it needs to be private. The best I can come up with is below:
def initialize
define_singleton_method(name) { ... }
self.singleton_class.send(:private, name)
end
Is there a way without having to use send to do this?
I overlooked class_eval:
define_singleton_method(name) { ... }
self.singleton_class.class_eval { private name }

Is there a better way to test this Ruby class with RSpec?

I'm extracting a subset of fields from a full JSON dataset having a JSON fixture. The better way I could think of is the following :
require "spec_helper"
# API ref.: GET /repos/:owner/:repo
# http://developer.github.com/v3/repos/
describe Elasticrepo::RepoSubset do
context "extract a subset of repository fields" do
let(:parsed) { Yajl::Parser.parse(fixture("repository.json").read) }
subject { Elasticrepo::RepoSubset.new(parsed) }
context "#id" do
its(:id) { should eq(2126244) }
end
context "#owner" do
its(:owner) { should eq("twitter") }
end
context "#name" do
its(:name) { should eq("bootstrap") }
end
context "#url" do
its(:url) { should eq("https://api.github.com/repos/twitter/bootstrap") }
end
context "#description" do
its(:description) { should eq("Sleek, intuitive, and powerful front-end framework for faster and easier web development.") }
end
context "#created_at" do
its(:created_at) { should eq("2011-07-29T21:19:00Z") }
end
context "#pushed_at" do
its(:pushed_at) { should eq("2013-04-13T03:56:36Z") }
end
context "#organization" do
its(:organization) { should eq("Organization") }
end
context "#full_name" do
its(:full_name) { should eq("twitter/bootstrap") }
end
context "#language" do
its(:language) { should eq("JavaScript") }
end
context "#updated_at" do
its(:updated_at) { should eq("2013-04-13T19:12:09Z") }
end
end
end
but I wonder if is there a better, smarter, cleaner or just more elegant way of doing that. The class I TDD out is this :
module Elasticrepo
class RepoSubset
attr_reader :id, :owner, :name, :url, :description, :created_at, :pushed_at,
:organization, :full_name, :language, :updated_at
def initialize(attributes)
#id = attributes["id"]
#owner = attributes["owner"]["login"]
#name = attributes["name"]
#url = attributes["url"]
#description = attributes["description"]
#created_at = attributes["created_at"]
#pushed_at = attributes["pushed_at"]
#organization = attributes["owner"]["type"]
#full_name = attributes["full_name"]
#language = attributes["language"]
#updated_at = attributes["updated_at"]
end
end
end
I would remove the individual context blocks. They don't add any additional information.
I'd use a map of keys/values and iterate, or create an object with the correct values and compare the entire object.

How can I assert on initialize behaviour with RSpec?

I have a message class, which can be initialized by passing arguments into the constructor, or by passing no arguments and then setting the attributes later with accessors. There is some pre-processing going on in the setter methods of the attributes.
I've got tests which ensure the setter methods do what they're supposed to, but I can't seem to figure out a good way of testing that the initialize method actually calls the setters.
class Message
attr_accessor :body
attr_accessor :recipients
attr_accessor :options
def initialize(message=nil, recipients=nil, options=nil)
self.body = message if message
self.recipients = recipients if recipients
self.options = options if options
end
def body=(body)
#body = body.strip_html
end
def recipients=(recipients)
#recipients = []
[*recipients].each do |recipient|
self.add_recipient(recipient)
end
end
end
I would tend to test the behaviour of the initializer,
i.e. that its setup the variables how you would expect.
Not getting caught up in the actuality of how you do it, assume that the underlying accessors work, or alternatively you could set the instance variables if you wanted. Its almost a good old fashioned unit test.
e.g.
describe "initialize" do
let(:body) { "some text" }
let(:people) { ["Mr Bob","Mr Man"] }
let(:my_options) { { :opts => "are here" } }
subject { Message.new body, people, my_options }
its(:message) { should == body }
its(:recipients) { should == people }
its(:options) { should == my_options }
end

Resources