Refer to method from containing scope in resource in chef recipe - ruby

In a chef recipe I define a helper method like:
def my_service_config(description, args="")
{
Unit: {
Description: description,
},
Service: {
Type: 'simple',
ExecStart: "/usr/sbin/my-service-script #{args}",
Restart: 'on-failure',
}
}
end
systemd_unit "my-service.service" do
content my_service_config("my description", "--some-flag")
action :create
end
But when I run chef I get an error like:
NoMethodError
-------------
undefined method `my_service_config' for Chef::Resource::SystemdUnit
How can I properly refer to the my_service_config method from inside the systemd_unit resource block?

I think you should use Chef libraries to write pure Ruby code. You can extend the functionality of a Chef recipe using methods such as my_service_config in a library. This method can be put into a "helper" file in libraries/ directory.
For example in libraries/helper.rb:
class Chef::Recipe
def my_service_config(description, args="")
{
Unit: {
Description: description,
},
Service: {
Type: 'simple',
ExecStart: "/usr/sbin/my-service-script #{args}",
Restart: 'on-failure',
}
}
end
end
Then in recipe:
service_content = my_service_config("my description", "--some-flag")
systemd_unit "my-service.service" do
content service_content
action :create
end

Related

Upload image with RSpec has unexpected class on controller

I'm in trouble with RSpec and fixture_file_upload on post request.
Here's the problem: I'm trying to send an image by upload to create a category with image, but when the image arrives, it changes it's class type.
I'm waiting to get in params[:category][:image] an ActionDispatch::Http::UploadedFile but what I receive is ActionController::Parameters.
My request example:
context 'when creates a new main category with valid params' do
let(:category) do
{ category: { name: 'Bolos E bolos',
description: 'São bolos sim',
locale: 'pt-BR',
image: fixture_file_upload('images/test-image.png', 'image/png') } }
end
post '/v4/categories' do
it 'upload image' do
expect { do_request(category) }.to change { ActiveStorage::Blob.count }.from(0).to(1)
end
end
end
what I got:
Failure/Error: expect { do_request(category) }.to change { ActiveStorage::Blob.count }.by(1)
expected `ActiveStorage::Blob.count` to have changed by 1, but was changed by 0
How do I send the image as upload and get it on the controller as ActionDispatch::Http::UploadedFile instead ActionController::Parameters
I could not get your controller spec working, but I managed to get an equivalent request spec working. Having spent 45+ minutes getting no where, I think this is the best I can do. This seems to work. (Just make sure you have an avatar.jpg in the public folder of your rails app.)
## spec/requests/user_spec.rb
require 'rails_helper'
RSpec.describe "Users", type: :request do
describe "it attaches uploaded files" do
it 'attaches the uploaded file' do
file = fixture_file_upload(Rails.root.join('public', 'avatar.jpg'), 'image/jpg')
expect {
post api_users_path, params: { user: {username: "Ben", avatar: file } }
}.to change(ActiveStorage::Attachment, :count).by(1)
end
end
end

Ruby 2.5 and FactoryBot raise TypeError

I'm in the process of upgrading a Rails (5.1) app from using Ruby 2.3.3 to Ruby 2.5.0.
I have the following FactoryBot (4.8.2) factory
FactoryBot.define do
factory :training do
transient do
assigned_instructor { create(:user) }
end
description 'training description'
instructor { assigned_instructor }
end
end
The users factory looks like
FactoryBot.define do
factory :user do
sequence(:full_name) { |n| "John Doe#{n}" }
sequence(:email) { |n| "john.doe#{n}#example.com" }
role { create(:role, activities: [create(:activity, name: 'activity')]) }
end
end
When I run any spec that creates a training I get
Failure/Error: assigned_instructor { create(:user) }
TypeError:
coerce must return [x, y]
This does not happen if I create the user and pass it to the factory as below.
create(:training, assigned_instructor: create(:user))
Is there any change in Ruby 2.5 I should be aware of?
FYI, all the specs that create users work as expected.

expect_any_instance_of with an object argument

I am testing a class's initialization block as below
class A
attr_accessor :client
def initialize(options, configuration)
self.client = B.new(options)
config = C.new(
url: configuration[:url],
headers: configuration[:headers],
username: configuration[:username],
password: configuration[:password]
)
client.configure(config)
end
end
class C
def initialize(options)
# does something with options hash
end
end
class B
def initialize(options)
# does something with options hash
end
def configure(config)
# some configuration with config object
end
end
My test case is as follows:
let(:options) {
{
force_basic_auth: true
}
}
let(:configuration) {
{
url: 'https://localhost:3000',
headers: { awesome: true },
username: 'test',
password: 'pass'
}
}
let(:api_config) {
C.new(configuration)
}
it 'configures object with passed params' do
expect_any_instance_of(B).to receive(:configure)
.with(api_config)
A.new(
options,
configuration
)
end
This fails my test case because the object that is created in the initialization block has a different object_id than the object_id of api_config which I am using in the expectations.
-[#<C:0x00000002b51128 #url="https://localhost:3000", #headers={:awesome=>true}, #username="test", #password="pass">]
+[#<C:0x00000002a1b628 #url="https://localhost:3000", #headers={:awesome=>true}, #username="test", #password="pass">]
Seeing that failure I was thinking whether it's a best practice to pass such objects directly in the initialization block. I mean I can fix it by directly passing the object in the initialization block.
There are many functions which are initializing the A class with a hash option being passed because of which I am doing it in the current way.
Is there a way to expect the contents of the object passed in rspec instead of verifying the objects are same ? Is passing the object directly in the initialization a more better approach ?
You can define arbitrary expectation handling to check the value of the parameter checked (see here):
it 'configures object with passed params' do
expect_any_instance_of(B).to receive(:configure) do |config|
expect(config).to be_a(C)
expect(config.url).to eq(configuration[:url])
expect(config.headers).to eq(configuration[:headers])
# ...
end
A.new(
options,
configuration
)
end
You want the configuration hash (rather than the object) under B.configure(config), so your class has to change slightly to accommodate.
Class file
class A
attr_accessor :client
def initialize(options, configuration)
self.client = B.new(options)
config = C.new(
url: configuration[:url],
headers: configuration[:headers],
username: configuration[:username],
password: configuration[:password]
)
client.configure(config.options)
end
end
class C
attr_reader :options
def initialize(options)
#options = options
end
end
class B
def initialize(options)
# does something with options hash
end
def configure(config)
# some configuration with config object
end
end
Here's what your RSpec code would look like.
describe do
let(:options) do
{
force_basic_auth: true
}
end
let(:configuration) do
{
url: 'https://localhost:3000',
headers: { awesome: true },
username: 'test',
password: 'pass'
}
end
let(:my_a_object) { A.new(options, configuration) }
let(:my_b_object) { B.new(options) }
it 'configures object with passed params' do
allow(B).to receive(:new).with(options).and_return(my_b_object)
expect(my_b_object).to receive(:configure).with(configuration)
my_a_object
end
end

How to create indexes with elasticsearch-model, bulk index doesn't work

I use elasticsearch-model gem to integrate elasticsearch with activerecord, I am using this module:
#http://www.codinginthecrease.com/news_article/show/409843?referrer_id=
module PostImport
def self.import
Post.find_in_batches do |posts|
bulk_index(posts)
end
end
def self.prepare_records(posts)
posts.map do |post|
{ index: { _id: post.id, data: post.as_indexed_json } }
end
end
def self.delete_index
Post.__elasticsearch__.client
.indices.delete index: ::Post.__elasticsearch__.index_name rescue nil
end
def self.create_index
Post.__elasticsearch__.create_index!
end
def self.bulk_index(posts)
Post.__elasticsearch__.client
.bulk({
index: ::Post.__elasticsearch__.index_name,
type: ::Post.__elasticsearch__.document_type,
body: prepare_records(posts),
refresh: true
})
end
end
According to elasticsearch-model example
It uses this to create indexes in bulk:
client.bulk index: 'articles',
type: 'article',
body: Article.all.as_json.map { |a| { index: { _id: a.delete('id'), data: a } } },
refresh: true
But this raises:
Elasticsearch::Transport::Transport::Errors::NotFound: [404] {"error":"IndexMissingException[[posts] missing]","status":404}
from /home/ubuntu/.rvm/gems/ruby-2.1.3/gems/elasticsearch-transport-1.0.6/lib/elasticsearch/transport/transport/base.rb:132:in `__raise_transport_error'
So the equivalent code in my module: PostImport::import raises this exception, If I call Post.__elasticsearch__.create_index! first, and then do import, it works fine.
What is the proper way to initialize elasticsearch and create indexes with elasticsearch-model gem?
You can use the elasticsearch-rails gem for this. See documentation here:
https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails
To facilitate importing data from your models into Elasticsearch, require the task definition in your application, eg. create the lib/tasks/elasticsearch.rake file with this content:
require 'elasticsearch/rails/tasks/import'
To import the records from your Article model for the first time and create the index, run this:
$ bundle exec rake environment elasticsearch:import:model CLASS='Article' FORCE=y

Why is my rspec test doubling my objects?

This is one of those cases where my code is working but my test is failing and I need to know what I am doing wrong?
I have a Project class with an all method that just spits out instances of this class:
class Project
##all_projects = []
def initialize(options)
##all_projects << self
end
def self.all
##all_projects
end
end
Now Project.all works just fine but the spec I am writing doesn't.
context "manipulating projects" do
before do
options1 = {
name: 'Building house'
}
options2 = {
name: 'Getting a loan from the Bank'
}
#project1 = Project.new(options1)
#project2 = Project.new(options2)
end
it "can print all projects" do
Project.all.should eq([#project1, #project2])
end
The failure message I get is:
Project manipulating projects can print all projects
Failure/Error: Project.all.should eq([#project1, #project2])
expected: [Building house, Getting a loan from the Bank]
got: [Building house, Building house, Building house, Getting a loan from the Bank, Building house, Getting a loan from the Bank]
Here is the full spec in a gist: https://gist.github.com/4535863
What am I doing wrong? How can I fix it?
It is doubling the results because it runs the before block for each test, where the class attribute is modified (when two new projects are initialized), and (according to the gist) the test you're referring to is the second one.
To avoid the problem you'll need to reset ##all_projects in an after block:
after do
Project.class_variable_set :##all_projects, []
end
See also: How can I clear class variables between rspec tests in ruby
(Thanks to #iain for the suggestion to move the reset code to an after block rather than a before block.)
This doesn't use before blocks to set stinky instance variables.
describe Project do
let(:options1){
{
name: 'Building house',
priority: 2,
tasks: []
}
}
let(:options2) {
{
name: 'Getting a loan from the Bank',
priority: 3,
tasks: []
}
}
let(:project1) { Project.new(options1) }
let(:project2) { Project.new(options2) }
context "while starting up" do
subject { Project.new options1 }
its(:name) { should include('Building house') }
its(:tasks) { should be_empty }
end
context "manipulating projects" do
before :all do
Project.all.clear
end
subject { Project.all }
its(:count) { should be > 0 }
it { should eq [project1, project2] }
end
end

Resources