How to insert data in the table without substituting the variable? - ruby

I have a rake file that create a lot of items.
namespace :db do
namespace :seed do
desc "items table"
task :items=> :environment do
Item.create(name: "first_name", process: "clean the item #{test} then pack and send to #{location}")
................................................
end
end
end
when i do rake db:seed:items I am not able to insert this data in the table without substituting the variable. Is there a way to insert this data without variable substitution so that i can substitute the variable later?

If you want to defer interpolation to a later time and still use the Ruby string interpolation notation, you can do this:
string = 'clean the item #{test} then pack and send to #{location}'
values = {
test: "paperclip",
location: "head office"
}
string.gsub(/\#\{([^}]+)\}/) do
values[$1.to_sym]
end
# => "clean the item paperclip then pack and send to head office"
You can even wrap this up into a simple method:
def interpolate(string, values)
string.gsub(/\#\{([^}]+)\}/) do
values[$1.to_sym]
end
end
Which, if you wanted to be a bit bold, you could patch into String:
class String
def interpolate(values)
self.gsub(/\#\{([^}]+)\}/) do
values[$1.to_sym]
end
end
end
Note that this only does the most basic interpolation of things like #{x} and not #{x.method_call} or #{x+1} or even #{x[y]}. For that you may need to go with a more arbitrary code evaluation method, but that road is paved with danger.

Related

Creating nested Puppet fact (Ruby) by iterating over gem query output

I have working Ruby code to query DNS details and create Puppet custom facts (puppet 5, Facter 3.11.6) however I am trying to modify it to create nested facts from the key/value pairs that the query obtains.
Code that works to set individual facts with the key name is:
require 'resolv'
Resolv::DNS::Config.default_config_hash.each do | key, value |
if !value.nil?
Facter.add("dns_#{key}") do
if value.is_a?(Array)
setcode { value.join(',') }
else
setcode { value }
end
end
end
end
which creates individual facts thus:
dns_nameserver => 192.168.1.1,192.168.1.2
dns_ndots => 1
dns_search => test.domain
My failed attempt so far to create a nested fact under the parent fact of 'DNS' is:
require 'resolv'
Facter.add("dns") do
value ={}
Resolv::DNS::Config.default_config_hash.each do | key, result |
if !result.nil?
if result.is_a?(Array)
setcode { value['#{key}'] = result.join(',') }
else
setcode { value['#{key}'] = result }
end
end
end
end
which gives a limited result of just:
dns => 1
Other code I have tried seems to put an array output into the string and multiple IPs are quoted inside square brackets over 2 lines instead of being output as per the first code block at top of page.
The fact structure I am TRYING to achieve (by modifying the top of page code) is:
dns => {
nameserver => 192.168.1.1,192.168.1.2,
ndots => 1,
search => test.domain,
}
Thanks in advance for any assistance.
I finally got this with the assistance from a poster who put some great code leads here, but unfortunately removed it soon afterward. Here is the code that works:
require 'resolv'
Facter.add(:networking_dns) do
setcode do
Resolv::DNS::Config.default_config_hash.each_with_object({}) do | (key, value), sub|
if !value.nil?
sub[key] = value
sub
end
end
end
end
Now for some explanatory notes (please feel free to correct me or offer any optimisations to this):
# the resolv gem is required
require 'resolv'
# create the parent fact (has no value of its own)
Facter.add(:networking_dns) do
# start building instructions in the fact
setcode do
# use the resolv gem to lookup values in /etc/resolv.conf and add .each to process all key/value pairs returned
# also add _with_object({}) and sub in the variables to set a blank value for sub. Saves doing it separately. Sub can be any name but denotes the declaration for the nested facts
Resolv::DNS::Config.default_config_hash.each_with_object({}) do | (key, value), sub|
# create facts only when the value is not nil
if !value.nil?
sub[key] = value
sub
# using a closing blank entry for a nested fact is critical or they won't create! Place this outside of the case statement to prevent blank values
end
end
end
end
# use the appropriate number of ends and indent for readability
Thanks to the person who posted their guidance here before removing it. I would like to upvote you if you post again.
Any tips on optimisation to the able solution are welcome, as I'm still grasping Ruby (spent hours on this!)

Naming different files. Handler? IO? Stream? Processor? Controller?

I'm having some trouble naming some files that I wrote. I don't really know the different between a stream, I/O, a handler, a processor (Is this a real concept?), and a controller. These are what my files look like in Ruby:
Starting from the rakefile:
desc "Calculate chocolate totals from a CSV of orders"
task :redeem_orders, [:orders_csv_path, :redemptions_csv_path] do |t, args|
args.with_defaults(:orders_csv_path => "./public/input/orders.csv", :redemptions_csv_path => "./public/output/redemptions.csv")
DataController.transfer(
input_path: args[:orders_csv_path],
output_path: args[:redemptions_csv_path],
formatter: ChocolateTotalsFormatter,
converter: ChocolateTotalsConverter
)
end
Then the controller (which in my mind delegates between different classes with the data obtained from the rakefile):
class DataController
def self.transfer(input_path:, output_path:, formatter:, converter:)
data_processor = DataProcessor.new(
input_path: input_path,
output_path: output_path,
formatter: formatter
)
export_data = converter.convert(data_processor.import)
data_processor.export(export_data)
end
end
The processor (which performs imports and exports according to the various files that were passed into this file):
class DataProcessor
attr_reader :input_path,
:output_path,
:formatter,
:input_file_processor,
:output_file_processor
def initialize(input_path:, output_path:, formatter:)
#input_path = input_path
#output_path = output_path
#formatter = formatter
#input_file_processor = FileProcessorFactory.create(File.extname(input_path))
#output_file_processor = FileProcessorFactory.create(File.extname(output_path))
end
def import
formatter.format_input(input_file_processor.read(input_path: input_path))
end
def export(export_data)
output_file_processor.write(
output_path: output_path,
data: formatter.format_output(export_data)
)
end
end
the converter referenced in the controller looks like this (it converts data that was passed in to a different format... I'm more confident about this naming):
class ChocolateTotalsConverter
def self.convert(data)
data.map do |row|
ChocolateTotalsCalculator.new(row).calculate
end
end
end
And the FileProcessorFactory in the above code snippet creates a file like this one that actually does the reading and the writing to CSV:
require 'csv'
class CSVProcessor
include FileTypeProcessor
def self.read(input_path:, with_headers: true, return_headers: false)
CSV.read(input_path, headers: with_headers, return_headers: return_headers, converters: :numeric)
end
def self.write(output_path:, data:, write_headers: false)
CSV.open(output_path, "w", write_headers: write_headers) do |csv|
data.each do |row|
csv << row
end
end
end
end
I'm having trouble with naming. Does it looks like I named things correctly? What should be named something like DataIO vs DataProcessor? What should a file named DataStream be doing? What about something that's a converter?
Ruby isn't a kingdom of nouns. Some programmers hear "everything is an object" and think "I am processing data, therefore I need a DataProcessor object!" But in Ruby, "everything is an object". There's only one novel "thing" in your example: a chocolate order (maybe redemptions, too). So you only need one custom class: ChocolateOrder. The other "things" we already have objects for: CSV represents the CSV file, Array (or Set or Hash) can represent the collection of chocolate orders.
Processing a CSV row into an order, converting an order into workable data, and totaling those data into a result aren't "things". They're actions! In Ruby, actions are methods, blocks, procs, lambdas, or top-level functions*. In your case I see a method like ChocolateOrder#payment for getting just the price to add up, then maybe some blocks for the rest of the processing.
In pseudocode I imagine something like this:
# input
orders = CSV.foreach(input_file).map do |row|
# get important stuff out of the row
Order.new(x, y, z)
end
# processing
redemptions = orders.map { |order| order.get_redemption }
# output
CSV.open(output_file, "wb") do |csv|
redemptions.each do |redemption|
# convert redemption to an array of strings
csv << redemption_ary
end
end
If your rows are really simple, I would even consider just setting headers:true on the CSV so it returns Hash and leave orders as that.
* Procs, lambdas, and top-level functions are objects too. But that's beside the point.
This seems like quite a 'java' way of thinking - in Ruby I haven't seen patterns like this used very often. I'd say that you might only really need the DataProcessor class. CSVProcessor and ChocolateTotalsConverter have only class methods, which might be more idiomatic if they were instance methods of DataProcessor instead. I'd start there and see how you feel about it.

RSpec mocking, `name` not available from within an example group

I have the following Ruby code:
def report_deviation(departure)
deviation = departure.fetch('Dev')
trip = departure.fetch('Trip')
run_id = trip.fetch('RunId')
headsign = trip.fetch('InternetServiceDesc')
timestamp = Time.now.strftime '%l:%M %P'
FileUtils.mkdir 'log' unless File.directory? 'log'
File.open DAILY_LOG_FILE, 'a' do |file|
file.puts "#{timestamp}, #{name}: Run #{run_id} (#{headsign}), deviation #{deviation}"
end
end
Tested by the following RSpec code:
describe 'report_deviation' do
let(:departure) { double }
let(:trip) { double }
let(:file) { double }
it 'appends to a log file with the correct entry format' do
expect(departure).to receive(:fetch).with('Trip').and_return trip
expect(departure).to receive(:fetch).with('Dev').and_return 'DEVIATION'
expect(trip).to receive(:fetch).with('RunId')
.and_return 'RUN'
expect(trip).to receive(:fetch).with('InternetServiceDesc')
.and_return 'HEADSIGN'
stub_const 'DeviationValidator::DAILY_LOG_FILE', :log_file
expect(File).to receive(:open).with(:log_file, 'a').and_yield file
timestamp = '12:00 pm: Run RUN (HEADSIGN), deviation DEVIATION'
expect(file).to receive(:puts).with timestamp
Timecop.freeze(Time.new 2017, 7, 31, 12) { report_deviation(departure) }
end
end
But when I run I receive the failure message:
`name` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).
The word name isn't written anywhere in here, and if I remove the final line of the test (which invokes the actual code) I get the test failures I would expect for unsatisfied exceptions. I normally would boil my code down to the pieces that are causing the error, but I have no idea what's causing the error.
For what it's worth, the specific line number mentioned in the backtrace is the file.puts within the File.open block - but I don't understand why that should cause a failure. I've set up test doubles such that those objects are nothing special - File receives open and yields file, whose only job is to listen for receiving puts with the string I expect. So what piece of code is calling what happens to be a keyword RSpec method name?
The problem is from rspec gem, if you are using Rails 6 you need to use gem 'rspec-rails', '~> 4.1.0'
name is not a keyword RSpec method, it's a method that report_deviation is trying to call
file.puts "#{timestamp}, #{name}: Run #{run_id} (#{headsign}), deviation #{deviation}"
but the method is not defined.
You need to define the name method in the class where report_deviation is defined. Or, if report_deviation is defined and used in the spec file, add a simple variable called name:
describe 'report_deviation' do
let(:departure) { double }
let(:trip) { double }
let(:file) { double }
let(:name) { "simple name" }
...
`name` is not available from within an example (e.g. an `it` block) [...]
I had a similar problem today. The final solution for the issue for now with a monkeypatch to go back to using method_name.
Create config/initializers/monkeypatches.rb file and fill inside with the following lines.
# config/initializers/monkeypatches.rb
#
# This fixes what seems to be a bug introduced by
# https://github.com/rails/rails/pull/37770
# "Modify ActiveRecord::TestFixtures to not rely on AS::TestCase:"
#
module ActiveRecord::TestFixtures
def run_in_transaction?
use_transactional_tests &&
!self.class.uses_transaction?(method_name) # this monkeypatch changes `name` to `method_name`
end
end
Credits: https://github.com/graphql-devise/graphql_devise/issues/42

How does a YAML double exclamation point work in this i18n gem?

I'm not using Rails and I haven't done any internationalization before, so I'm trying to understand how this particular example works but I'm a little bit stumped:
The r18n-desktop gem reads from a YAML file for translations. Pretty straightforward.
YAML file en.yml:
user:
edit: Edit user
name: User name is %1
count: !!pl
1: There is 1 user
n: There are %1 users
log:
signup: !!gender
male: Он зарегистрировался
female: Она зарегистрировалась
Test ruby code:
require 'r18n-desktop'
R18n.from_env('./localizations/')
R18n::Filters.add('gender') do |translation, config, user|
puts translation
puts config
puts user
translation[user.gender]
end
include R18n::Helpers
class Ayy
attr_accessor :gender
end
girl = Ayy.new
girl.gender = :female
puts t.user.count(5)
puts t.log.signup girl
Output:
There are 5 users
localization-test.rb:13:in
puts: can't convert R18n::Translation to Array (R18n::Translation#to_ary gives R18n::Untranslated) (TypeError) from localization-test.rb:13:in puts' from localization-test.rb:13:in '
Addenum: Looks like the error is in puts rather than the "translation". The actual result of a translation is log.signup[] though so the gender isn't getting through.
What is t.log.signup() expecting?
Seems like you forget to set a filter for !!gender custom type.
R18n has only few built-in filter — like !!pl. Gender filter is not built-in, you need to define it manually.
R18n Filter docs already contains simple filter example for gender:
R18n::Filters.add('gender') do |translation, config, user|
translation[user.gender]
end

How to list the description of all Revision within a Task?

Trying to print out Description of all Revisions within a task for a given set of tasks. But RevisionHistory seems to be a HashArray and not a RallyObject. Not sure how to proceed. Any help here would be appreciated. Here is a snippet of the direction I am taking. What should I replace the ------ with?
results.each do |task|
#revisions = task.rally_object['RevisionHistory'].-------
#revisions.each do |task_revision|
puts task_revision["Description"]
end
end
you shouldn't need to get at the RallyObject there, instead that class was meant to have convenience methods via method missing or [FieldName] to get at the fields for each object. For example try your loop as:
results.each do |task|
#revisions = task['RevisionHistory']['Revisions']
#you may need a #revisions = task['RevisionHistory'].read or something like that depending what you fetched in your query.
#revisions.each do |task_revision|
puts task_revision["Description"]
end
end
Hope that helps.

Resources