Why is my ruby class behaving unexpectedly? - ruby

I have the following ruby class:
class Scheme
attr_reader :id, :uri, :labels, :in_schemes, :top_concepts
def initialize(id, uri, labels, in_schemes)
#id = id,
#uri = uri,
#labels = labels
#in_schemes = in_schemes
#top_concepts = Array.new
end
end
And I have the following method that traverses the given directory looking for files (they have names like "01", "01.01.00", etc.) containing a series of language-specific category labels (Sample below):
def make_schemes(catdir)
concept_schemes = Array.new
Dir.foreach(catdir) do |file|
unless file == "." || file == ".."
id = file.gsub(/\./,"").to_s
uri = "/unbist/scheme/#{id}"
labels = Array.new
in_schemes = Array.new
File.read("#{catdir}/#{file}").split(/\n/).each do |line|
label = JSON.parse(line)
labels << label
end
if id.size > 2
in_schemes = ["/unbist","/unbist/#{id[0..1]}"]
else
in_schemes = ["/unbist"]
end
p "Making new concept scheme with id: #{id}"
concept_scheme = Scheme.new(id, uri, labels, in_schemes)
p concept_scheme
concept_schemes << concept_scheme
end
end
return concept_schemes
end
Sample category file, named "#{dir}/01". Each line is proper JSON, but the whole file, for reasons beyond the scope of this question, is not.
{ "text": "ﻢﺳﺎﺌﻟ ﻕﺎﻧﻮﻨﻳﺓ ﻮﺴﻳﺎﺴﻳﺓ", "language": "ar" }
{ "text": "政治和法律问题", "language": "zh" }
{ "text": "political and legal questions", "language": "en" }
{ "text": "questions politiques et juridiques", "language": "fr" }
{ "text": "ПОЛИТИЧЕСКИЕ И ЮРИДИЧЕСКИЕ ВОПРОСЫ", "language": "ru" }
{ "text": "cuestiones politicas y juridicas", "language": "es" }
The output I am getting is strange. The id variable in the make_schemes method is set properly prior to constructing the new Scheme, but the Scheme initializer seems to be confused somewhere and is applying the entire set of variables to the object's id variable. Here is some output for the above sample (cleaned newlines added for readability:
"Making new concept scheme with id: 01"
#<Scheme:0xa00ffc8
#uri="/scheme/01",
#labels=[{"text"=>"مسائل قانونية وسياسية", "language"=>"ar"}, {"text"=>"政治和法律问题", "language"=>"zh"}, {"text"=>"political and legal questions", "language"=>"en"}, {"text"=>"questions politiques et juridiques", "language"=>"fr"}, {"text"=>"ПОЛИТИЧЕСКИЕ И ЮРИДИЧЕСКИЕ ВОПРОСЫ", "language"=>"ru"}, {"text"=>"cuestiones politicas y juridicas", "language"=>"es"}],
#id=["01", "/scheme/01", [{"text"=>"مسائل قانونية وسياسية", "language"=>"ar"}, {"text"=>"政治和法律问题", "language"=>"zh"}, {"text"=>"political and legal questions", "language"=>"en"}, {"text"=>"questions politiques et juridiques", "language"=>"fr"}, {"text"=>"ПОЛИТИЧЕСКИЕ И ЮРИДИЧЕСКИЕ ВОПРОСЫ", "language"=>"ru"}, {"text"=>"cuestiones politicas y juridicas", "language"=>"es"}]],
#in_schemes=["/"],
#top_concepts=[]>
What am I missing here? What is causing this? I have a constructor for a different class that works fine with similar logic. I'm baffled. Maybe there's an approach that would work better?

Try fixing:
#uri = uri,
to:
#uri = uri
As is, you're telling Ruby:
#uri = uri, #labels = labels
Which, as I read it, means you're assigning labels to an array of uri, #labels, then assigning that array to #uri.

Related

Ruby exclude specific data from array of hashes

I've got response which hash and array of hashes:
"id"=>67547,
"description"=>"project",
"actors"=>
[
{"id"=>123,
"displayName"=>"John Doe",
"type"=>"atlassian-user-role-actor",
"name"=>"john.doe",
"actorUser"=>{"accountId"=>"some_id"}},
{"id"=>456,
"displayName"=>"Chris Sth",
"type"=>"atlassian-user-role-actor",
"name"=>"chris.sth",
"actorUser"=>{"accountId"=>"some_id"}},
{"id"=>789,
"displayName"=>"Testing Name",
"type"=>"atlassian-user-role-actor",
"name"=>"testing.name",
"actorUser"=>{"accountId"=>"some_id"}},
]
What I need is to pull the name for each hash['actors'] and convert it to the email address. The thing is I need to skip names which are defined as EXCLUDED_NAMES
EXCLUDED_NAMES = %w[
chris.sth
removed1258986304
john.doe
other.names
].freeze
private_constant :DEFAULT_EXCLUDED_NAMES
I was trying to something like below but still get all names:
def setup_email
dev_role['actors'].map do |user|
if user.include?(EXCLUDED_NAMES)
user.delete
else
"#{user['name']}#example.com"
end
end
end
You can get an array of valid emails with:
emails = dev_role['actors'].map do |user|
"#{user['name']}#example.com" unless EXCLUDED_NAMES.include?(user['name'])
end
Array will only contain 'testing.name#example.com'
If dev_role['actors'] is this:
[
{"id"=>123,
"displayName"=>"John Doe",
"type"=>"atlassian-user-role-actor",
"name"=>"john.doe",
"actorUser"=>{"accountId"=>"some_id"}},
{"id"=>456,
"displayName"=>"Chris Sth",
"type"=>"atlassian-user-role-actor",
"name"=>"chris.sth",
"actorUser"=>{"accountId"=>"some_id"}},
{"id"=>789,
"displayName"=>"Testing Name",
"type"=>"atlassian-user-role-actor",
"name"=>"testing.name",
"actorUser"=>{"accountId"=>"some_id"}},
]
then it is certain that user in each block would be a Hash object:
{
"id"=>123,
"displayName"=>"John Doe",
"type"=>"atlassian-user-role-actor",
"name"=>"john.doe",
"actorUser"=>{"accountId"=>"some_id"}
}
So, doing user["name"], should produce: "john.doe".
Now, that we have an exclusion list EXCLUDED_NAMES we could use include? like so on it:
EXCLUDED_NAMES.include?(user["name"])
=> # true if the name is in the EXCLUDED_NAMES
So, all you need is a small change in your code to fix the condition:
def setup_email
dev_role['actors'].map do |user|
if EXCLUDED_NAMES.include?(user["name"])
user.delete
else
"#{user['name']}#example.com"
end
end
end
There is one problem though, the user.delete would not work as it expects an argument that is supposed to be a key to the hash object.
This can be fixed through by using reject or select(changing to reject as it reads better):
def setup_email
dev_role['actors'].reject do |user|
EXCLUDED_NAMES.include?(user["name"])
end.map{ |user| user["name"] }
end
The nature of the method seems to be returning an array/list, so I would insist that the name of such methods should be plural: setup_emails.
I'd create a lookup hash based upon the the actor name. Then retrieve the values that are not in EXCLUDED_NAMES.
When actors can contain duplicate names:
actors = dev_role['actors'].group_by { |actor| actor['name'] }
actors = actors.values_at(*actors.keys - EXCLUDED_NAMES).flatten(1)
When actors can't contain duplicate names:
actors = dev_role['actors'].to_h { |actor| [actor['name'], actor] }
actors = actors.values_at(*actors.keys - EXCLUDED_NAMES)
Then:
emails = actors.map { |actor| "#{actor['name']}#example.com" }
You could also solve this with an Array#reject/Array#map combination:
emails = dev_role['actors']
.reject { |actor| EXCLUDED_NAMES.include?(actor['name']) }
.map { |actor| "#{actor['name']}#example.com" }
The above might be slower when using a large EXCLUDED_NAMES array.
dev_role=dev_role.to_hash
actors=dev_role["actors"]
for each_actor in actors
if EXCLUDED_NAMES.include?(each_actor["name"])==false
p "#{each_actor['name']}#example.com"
end
end

passing arguments in rspec shared example

I am a bit new with Rspec.
Here is my problem
I have an example which could be shared
shared example
RSpec.shared_examples "coupons_shared" do |arg1,email,coupon1,coupon2|
it "present_coupons" do
post_rest_url = "https://blahblahblah=" + "#{arg1}" + "&email=" + "#{email}"
json_request = <<END_OF_MESSAGE
[
"#{coupon1}",
"#{coupon2}"
]
END_OF_MESSAGE
header = {:accept => "application/json",:content_type => "application/json"}
resp = RestClient.post(post_rest_url, json_request, header)
json_obj = JSON.parse(resp)
expect(json_obj[0]["ccode"]).to include("#{coupon1}")
expect(json_obj[0]["ccode"]).to include("#{coupon2}")
end
end
shared example file location is in \spec\support\shared_examples
In the actual spec file I have an example which gets the coupon and then need to present using the shared example
describe "enrol_cust" do
cust_id = '1'
coupons = []
header_basic_auth = {:accept => "application/json",:content_type => "application/json"}
random_no = (rand(999) + 10)
random_no = random_no.to_s
email = "johndoe" + "#{random_no}" + "#gmail.com"
st = 1111
st = st.to_i
before(:each) do
#dob = 20.years.ago(Date.today).strftime("%m-%d-%Y")
end
it "enrol_cust" do
post_rest_url = "https://blahblah?st=" + "#{st}"
json_request = <<END_OF_MESSAGE
{
"email": "#{email}",
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "#{#dob}",
}
END_OF_MESSAGE
header = header_basic_auth
resp = RestClient.post(post_rest_url, json_request, header)
json_obj = JSON.parse(resp)
cust_id = json_obj["cid"]
end
# above example gets customer id
it "get_list" do
get_rest_url = "https://blahblah=" + "#{cust_id}" + "&st=" + "#{st}"
header = header_basic_auth
resp = RestClient.get(get_rest_url, header)
json_obj = JSON.parse(resp)
coupons = json_obj.collect {|x| x["cccode"]}
end
# above example gets coupons
# I have tried printing out the coupons and I can see the correct coupons in this example
include_examples "coupons_shared" ,"#{st}","#{email}","#{coupons[0]}","#{coupons[1]}"
When I try to pass the parameters, st and email is passed correctly. However, coupons[0] and coupons[1] is always passed as ""
I am not sure what am I missing here.
To pass an argument to a shared example, wrap the variable inside the example block (without listing them as you have as arguments in the example):
RSpec.shared_examples "coupons_shared" do
...code that includes your variables coupon1, coupon2 etc..
end
include_examples "coupons_shared" do
coupon1 = coupons[0]
...and so on (also works with let)...
let(:coupon1) { coupons[0] }
end
I would also strongly suggest that you stub out the HTTP requests so they aren't hitting an actual server every time you run your tests and consider using FactoryBot (or fixtures if that's your preference) to clean up a lot of the variable assignments.

Ruby String #{} doesn't work

I have this is my code:
class Template
def initialize(temp_str)
#str = temp_str
end
def render options={}
#str.gsub!(/{{/,'#{options[:').gsub!(/}}/,']}')
puts #str
end
end
template = Template.new("{{name}} likes {{animal_type}}")
template.render(name: "John", animal_type: "dogs")
I was hoping the result would be John likes dogs, but it was
#{options[:name]} likes #{options[:animal_type]}
Why doesn't the #{} get interpolated?
#{} is not some magic that gets converted to interpolation whenever it occurs. It's a literal syntax for interpolating. Here you are not writing it literally, you get it by doing a replacement. Instead, you could do something like:
template = "{{name}} likes {{animal_type}}"
options = {name: 'John', animal_type: 'dogs'}
template.gsub(/{{(.*?)}}/) { options[$1.to_sym] } # => "John likes dogs"
This captures the name inside the moustaches and indexes the hash with it.
Even better would be to utilize the existing format functionality. Instead of moustaches, use %{}:
template = "%{name} likes %{animal_type}"
options = {name: 'John', animal_type: 'dogs'}
template % options # => "John likes dogs"

Parse and substitute markup in Ruby

I want to parse a simple string like:
"My name is **NAME**" to "My name is <strong>NAME</strong\>"
Note:
Cannot use any external gems, even though markdown gem might have done the job.
If I understood you correctly it should be quite simple:
text = "My name is **NAME**"
=> "My name is **NAME**"
text = text.gsub(([a-zA-Z\s]*)(\*\*)([a-zA-Z\s]*)(\*\*)/,"\\1<strong>\\3</strong>")
=> "My name is <strong>NAME</strong>"
I've tested it in irb with this command text.gsub(([a-zA-Z\s]*)(\*\*)([a-zA-Z\s]*)(\*\*)/,"\\1<strong>\\3</strong>")
UPDATED
Consider this, if you wish to handle some more cases:
class SurroundMarkup
def initialize(markup_map: {})
#markup_map = markup_map
end
def format(text)
text.tap do |formatted_text|
#markup_map.each do |markup, tag|
formatted_text.gsub!(/#{markup}(?<text>.*?)#{markup}/) do |m|
start_tag(tag) + $~[:text] + stop_tag(tag)
end
end
end
end
private
def start_tag(tag)
"<#{tag}>"
end
def stop_tag(tag)
"</#{tag}>"
end
end
And you can use as follows:
markup_map = {
/\*\*/ => "strong",
/\*/ => "i",
}
s = SurroundMarkup.new(markup_map: markup_map)
s.format("My name is **NAME**") #=> "My name is <strong>NAME</strong>"
s.format("*Ruby* is cool") #=> "<i>Ruby</i> is cool"

Ruby array of objects subtraction by object value

I have two arrays which contain objects of assets, now I want to subtract to get only objects from the first array which the second array doesn't have. I should use "-" right?
Here is my object
class Asset
attr_accessor :id, :url, :text, :img_url, :type, :created_at
def initialize(url, text, type, img_url, created_at)
#id, #url, #text, #img_url, #type, #created_at = "", url, text, img_url, type, created_at
end
def eql?(another_asset)
self_domain = UrlUtils::get_domain(self.url)
another_asset_domain = UrlUtils::get_domain(another_asset.url)
if self_domain == 'youtube' && another_asset_domain == 'youtube'
self_youtube_id = UrlUtils::get_parameter(self.url, "v")
another_asset_youtube_id = UrlUtils::get_parameter(another_asset.url, "v")
return self_youtube_id.eql?(another_asset_youtube_id)
end
return self.url.eql?(another_asset.url)
end
def hash
#created_at.hash + 32 * #url.hash
end
end
The idea is one asset can contain url from youtube which every url might be different but it's the same video, so I have to compare each url with parameter "v" (youtube_id).
And this is my test which is wrong at the moment, because it doesn't do the subtraction correctly.
it "should substract duplicated youtube from mixed assets" do
mixed_assets = Array.new
all_assets = Array.new
google = Asset.new("http://www.google.com", "", "", "", Time.now)
youtube = Asset.new("http://www.youtube.com?v=1", "", "", "", Time.now)
mixed_assets.push(google)
mixed_assets.push(youtube)
another_youtube = Asset.new("http://www.youtube.com?v=1&a=1", "", "", "", Time.now)
all_assets.push(another_youtube)
mixed_assets = mixed_assets - all_assets
mixed_assets.length.should eql 1
mixed_assets[0].url.should eql "http://www.google.com"
end
I'm very new to ruby and I did some research that I should implement "hash" method as well, but I couldn't find any example how to do that.
Thanks.
Array subtraction works via hashes, so you're correct. I couldn't test since I don't know what UrlUtils is, but something similar to the following is likely what you need added to the Asset class:
def hash
domain = UrlUtils::get_domain(self.url)
v = domain == 'youtube' ? UrlUtils::get_parameter(self.url, "v") : ''
domain.hash ^ v.hash
end
You might also need an eql? method. There's a bunch of additional information in this post that you probably will want to look over; it covers this, as well as a bunch of related topics.

Resources