I'm new to Ruby on Rails and going through Learn Ruby the Hard Way right now. In the lesson I am supposed to add the Assert feature to Dict.rb (shown below). Would anyone know how I can go about doing this? Every time I try I receive an error.
module Dict
#Creates a new function that makes a Dictionary. This is done through creating the
# aDict variable that has an array in which num_buckets array is placed inside.
# These buckets will be used to hold the contents of the Dict and later aDict.length
# is used to find out how many buckets there are.
def Dict.new(num_buckets=256)
# Initializes a Dict with the given number of buckets.
aDict = []
(0...num_buckets).each do |i|
aDict.push([])
end
return aDict
end
# Converts a string to a number using the bult-in Ruby 'hash' function
# Once I have a number for the key, I use the % operator and aDict.length to get a
# bucket where the remainder can go.
def Dict.hash_key(aDict, key)
# Given a key this will create a number and then convert it to an index for the
# aDict's buckets
return key.hash % aDict.length
end
# Uses hash_key to find a bucket that the key could be in. Using bucket_id I can get
# the bucket where the key could be. By using the modulus operator I know it will fit
# into the aDict array of 256.
def Dict.get_bucket(aDict, key)
# Given a key, find the bucket where it would go.
bucket_id = Dict.hash_key(aDict, key)
return aDict[bucket_id]
end
# Uses get_slot to get the (i, k, v) and returns the v (value) only.
def Dict.get_slot(aDict, key, default=nil)
# Returns the index, key and value of a slot found in a bucket.
bucket = Dict.get_bucket(aDict, key)
bucket.each_with_index do |kv, i|
k, v = kv
if key == k
return i, k, v
end
end
return -1, key, default
end
def Dict.get(aDict, key, default=nil)
# Gets the value in a bucket for the given key or the default.
i, k, v = Dict.get_slot(aDict, key, default=default)
return v
end
# Sets a key/value pair by getting the bucket and appending the new (key, value) to it.
# First you have to get the bucket, see if the key already exists, if it does then
# replace it, if it doesn't get replaced then append it.
def Dict.set(aDict, key, value)
# Sets the key to the value, replacing any existing value.
bucket = Dict.get_bucket(aDict, key)
i, k, v = Dict.get_slot(aDict, key)
if i >= 0
bucket[i] = [key, value]
else
bucket.push([key, value])
end
end
# Deletes a key by getting the bucket, searching for key in it and deleting it form the
# array.
def Dict.delete(aDict, key)
# Deletes the given key from the Dict.
bucket = Dict.get_bucket(aDict, key)
(0...bucket.length).each do |i|
k, v = bucket[i]
if key == k
bucket.delete_at(i)
break
end
end
end
# goes through each slot in each bucket and prints out what's in the Dict.
def Dict.list(aDict)
# Prints out what's in the Dict.
aDict.each do |bucket|
if bucket
bucket.each {|k, v| puts k, v}
end
end
end
end
I am using the following script to run methods from the module Dict.rb:
require './dict.rb'
# create a mapping of state to abbreviation
states = Dict.new()
Dict.set(states, 'Oregon', 'OR')
Dict.set(states, 'Florida', 'FL')
Dict.set(states, 'California', 'CA')
Dict.set(states, 'New York', 'NY')
Dict.set(states, 'Michigan', 'MI')
# create a basic set of states and some cities in them
cities = Dict.new()
Dict.set(cities, 'CA', 'San Francisco')
Dict.set(cities, 'MI', 'Detroit')
Dict.set(cities, 'FL', 'Jacksonville')
# add some more cities
Dict.set(cities, 'NY', 'New York')
Dict.set(cities, 'OR', 'Portland')
# puts out some cities
puts '-' * 10
puts "NY State has: #{Dict.get(cities, 'NY')}"
puts "OR State has: #{Dict.get(cities, 'OR')}"
# puts some states
puts '-' * 10
puts "Michigan's abbreviation is: #{Dict.get(states, 'Michigan')}"
puts "Florida's abbreviation is: #{Dict.get(states, 'Florida')}"
# do it by using the state then cities dict
puts '-' * 10
puts "Michigan has: #{Dict.get(cities, Dict.get(states, 'Michigan'))}"
puts "Florida has: #{Dict.get(cities, Dict.get(states, 'Florida'))}"
# puts every state abbreviation
puts '-' * 10
Dict.list(states)
# puts every city in state
puts '-' * 10
Dict.list(cities)
puts '-' * 10
# by default ruby says "nil" when something isn't in there
state = Dict.get(states, 'Texas')
if !state
puts "Sorry, no Texas."
end
# default values using ||= with the nil result
city = Dict.get(cities, 'TX', 'Does Not Exist')
puts "The city for the state 'TX' is: #{city}"
There is no "assert" method by default in Ruby. That's why you are getting an "undefined method" error. You either need to implement this method yourself or use a testing framework like Minitest or Rspec.
If you want to implement a simple assert method yourself it would be something like this:
def assert(first_item, second_item)
unless first_item == second_item
puts "[Error] #{first_item} does not equal #{second_item}"
end
end
And then use it like this:
assert(Dict.get(cities, 'NY'), 'New York')
The idea is that assertions help you make sure your code returns the expected output without having to check it manually.
Related
I have multiple columns I need to pull unique values from and compile an array of each unique value. Using uniq.pluck(:column_name) works, but how do I iterate over an array of column names?
react = []
fields = [reactivity_1, reactivity_2, reactivity_3, reactivity_4]
fields.each do |field|
puts "Parsing #{field}"
Raw.all.uniq.pluck(field).each do |r|
unless react.include? r
puts "Adding #{r} to Array."
react << r
else
puts "#{r} Exists."
end
end
end
Error:
NameError: undefined local variable or method `reactivity_1' for main:Object
You will need to make the column names strings or symbols, like this Ruby thinks it is a local varaible or method.
react = Set.new
fields = [:reactivity_1, :reactivity_2, :reactivity_3, :reactivity_4]
fields.each do |field|
puts "Parsing #{field}"
Raw.all.uniq.pluck(field).each do |r|
react << r
end
end
If you want to make sure that a collection does not contain duplicate, you can use a Set:
require "set"
set = Set.new
set << "foo"
set << "bar"
set << "bar"
puts set.size #> 2
I've rewritten your code sample to use Set.
Can you describe what you are trying to achieve? Perhaps there is an easier way to get the data out of the DB.
I have two tables. One for accounts and another for keywords. I would like to iterate over all of the keywords and store each one in a hash--grouped by the account ID that added the keyword. The code that I have below doesn't add each keyword to the hash. For example, I have an account that has 2 keyword entries. My code skips the first entry and only adds the second entry to the hash.
#keyword_hash = {}
#account.each do |key, value|
#keywords.where(:profile_id => key).each do |keyword|
#keyword_hash[key] = keyword.entry
end
end
puts #keyword_hash
How can I modify the above code so that I add each keyword entry for a particular account to the hash?
I would like to be able to do #keyword_hash[6] and get keyword1, keyword2, keyword3, etc. for that account. Thanks!
Make an array [keyword1, keyword2, keyword3, etc.] and then add it to hash
**
#keyword_hash = {}
#account.each do |key, value|
arr = []
#keywords.where(:profile_id => key).each do |keyword|
arr << keyword.entry
end
#keyword_hash[key] = arr
end
puts #keyword_hash
**
Try this code
#keyword_hash = Hash.new { |h, k| h[k] = [] }
#account.each do |key, value|
#keywords.where(:profile_id => key).each do |keyword|
#keyword_hash[key] << keyword.entry
end
end
puts #keyword_hash
The mistake you are doing is that you are storing a single value against each key in your #keyword_hash hash. so when your code writes second value against account key, it replaces the previous value instead of adding second one.
Edit: Thank you #mudasobwa for correction regarding shared default value.
#keyword_hash = Hash.new { |hash, key| hash[key] = [] }
#keywords.group_by{ |k| k.profile_id }.each do |key,value|
#keyword_hash[key] = value.map(&:entry)
end
puts #keyword_hash
After doing some more research, I found the above solution. I used #jvillian's suggestion about group_by, and I found this article that showed me how to initialize the hash.
I'm working on a code which displays the images from the AWS server. But I'm facing trouble in looping the code.
It works fine for the 1st display but it is not going further (I've to display upto 6 images)
code for this -
def get_image_urls(user)
user_identifications = user.user_identifications.where(current_flag: true).order(:id_dl)
urls = []
keys = []
if !user_identifications.empty? && !user_identifications.nil?
user_identifications.each_with_index do |each_id, index|
obj = S3_BUCKET.object(each_id.aws_key)
urls << {each_id.id_dl=> obj.presigned_url(:get)}
keys << {each_id.id_dl=> each_id.aws_key}
end
end
return urls, keys
end
How to increment the loop based on checking the id and user.identifications value?
reject all empty values and then iterate users_identifications.
Like:
sanitized_identifications = users_identifications.reject(&:blank?)
sanitized_identifications.each_with_index do |identification, _index|
# Now if you want to skip an iteration based on some condition, try `next`, like:
# next if some_condition
# in you case
obj = S3_BUCKET.object(each_id.aws_key)
next if obj.blank?
urls << {each_id.id_dl=> obj.presigned_url(:get)}
keys << {each_id.id_dl=> each_id.aws_key}
end
UPDATE
# ...
user_identifications.each_with_index do |each_id, index|
begin
obj = S3_BUCKET.object(each_id.aws_key)
urls << {each_id.id_dl=> obj.presigned_url(:get)}
keys << {each_id.id_dl=> each_id.aws_key}
rescue => e
next
end
end
#...
Cheers!
i am trying to keep all my logic out of views, and have come up with the following piece of code, the though im having is that it isnt returning the actual score value, it just returns if it was a Win, lost or tie
def find_result(schedule)
return "not required" if schedule.event != '1' or schedule.time >= Time.now
if schedule.for.nil? or schedule.against.nil?
"Not Entered"
else
tie = '<b>T</b> '
tie << schedule.for.to_i
tie << ' - '
tie << schedule.against.to_i
win = '<b>W</b> '
win << schedule.for.to_i
win << ' - '
win << schedule.against.to_i
return raw tie if schedule.for.to_i == schedule.against.to_i
schedule.for.to_i > schedule.against.to_i ? (raw win) : "Lost"
end
end
Don't use << with an integer. See the docs:
http://www.ruby-doc.org/core-1.9.3/String.html#method-i-3C-3C
It's probably turning your win/loss numbers into characters that aren't showing up in the HTML.
Use a formatter or something, or perhaps just to_s rather than to_i when appending the numbers.
Example using string format (untested):
def find_result(schedule)
return "not required" if schedule.event != '1' or schedule.time >= Time.now
if schedule.for.nil? or schedule.against.nil?
"Not Entered"
elsif schedule.for.to_i < schedule.against.to_i
"Lost"
else
raw "<b>%s</b> %d - %d" % [
schedule.for.to_i == schedule.against.to_i ? 'T' : 'W',
schedule.against.to_i,
schedule.for.to_i
]
end
Edit: Refactor
Keeping logic out of the views is good, but it would be even more appropriate to
move some of this to the model, namely the result of the schedule (not entered,
win, loss, tie)
In the example I'll make a simple inner class which encapsulates that logic, which
the Schedule makes use of to know its own result. You could do this any number of ways
though (e.g. a module versus a class, or methods directly on Schedule)
I'll then demonstrate how you might use the new schedule in your helper using the logic provided, or simply querying for the result itself and using it as a key for a translation lookup (I18n).
Note this is untested and a little bit pseudo-codey (I'm not using any I18n library in particular, just guessing at methods and translation formatting). But it should work with some tweaking, or at least give you an idea of another way of doing things.
class Schedule
# The schedule jus instantiates a result object when you ask for one.
# For convenience the result's to_s is it's value, e.g. "win"
def result
Result.new(self.for, self.against)
end
# delegate methods querying the result
delegate :win?, :loss?, :tie?, :not_entered?, :to => :result
class Result
Values = %(win loss tie not_entered)
Win = Values[0]
Loss = Values[1]
Tie = Values[2]
NotEntered = Values[3]
attr_reader :for, :against
def initialize(_for, against)
#for = _for
#against = against
end
def value
return NotEntered unless [#for, #against].all?
case v = #for - #against
when v.zero? then Tie
when v > 0 then Win
else Loss
end
end
alias :to_s :value
def not_entered?; self.value == NotEntered end
def win?; self.value == Win end
def loss?; self.value == Loss end
def tie?; self.value == Tie end
end
end
# then in your helper, something like
def find_result(schedule)
# you'd want to refactor this requirement part too
return "not required" if schedule.event != '1' or schedule.time >= Time.now
# Now you could do it essentially the way you had, with ifs or a
# case statement or what have you, but the logic for the result is kept
# where it belongs, on the class.
if schedule.not_entered?
"Not Entered"
elsif schedule.loss?
"Loss"
else
prefix = schedule.win? ? "W" : "T"
raw "<b>%s</b> %d - %d" % [prefix, schedule.for, schedule.against]
end
# OR you could use some kind of translation library using the `value`
# returned by the result. Something like:
key = ["schedule", schedule.outcome.value].join(".")
raw I18n.translate(key, {:for => schedule.for, :against => schedule.against})
end
# if you used the latter, it would lookup the translation in some other place,
# e.g. some config JSON, which might look like this (more or less, and
# depending on the lib you use):
{
"schedule": {
"win": "<b>W</b> {{for}} - {{against}}",
"tie": "<b>T</b> {{for}} - {{against}}",
"loss": "Loss",
"not_entered": "Not Entered"
}
}
# The translation has a few advantages. It would allow you to sub in other
# languages, but also, it conveniently keeps all of the app's text in one
# place, if you stick to using it.
given the following code from my custom model:
def categorize
#cv = Cv.find(params[:cv_id], :include => [:desired_occupations, :past_occupations, :educational_skills])
#menu = :second
#language = Language.resolve(:code => :en, :name => :en)
categorizer = CvCategorizer.new #cv, #language
categorizer.prepare_occupations
categorizer.prepare_occupation_skills
categorizer.prepare_education_skills
# fetch the data
#occupation_hashes = categorizer.occupations
#skill_hashes = categorizer.skills
# Sort the hashes
#occupation_hashes.sort! { |x,y| y.score <=> x.score}
#skill_hashes.sort! { |x,y| y.score <=> x.score}
#max = #skill_hashes.first.score
#min = #skill_hashes.last.score
end
The code creates a new instance of the CvCategorizer class and calls the three prepare methods in sequence. They all do funky stuff with data retrieved from the database. The code looks as follows:
# = CvCategorizer
# This class will handle the categorizing of a CV based upon the skills and occupations found in
# the CV. Because the controller originally had a huge chunk of code, this class will break up that
# login into seperate function calls and keep everything inside variables for easy access.
class CvCategorizer
# initializes a new instance of the CvCategorizer
def initialize cv, language
#cv = cv
#language = language
#occupations = []
#skills = []
end
# Prepares the occupation array by looking at the stored CV and collecting
# all the desired occupations and past occupations. These are stored inside
# the internal occupation array as a uniq list.
def prepare_occupations
all_occupations = #cv.desired_occupations.all(:include => :labels) + #cv.past_occupations.all(:include => :labels)
all_occupations.each do |occupation|
oc = OccupationCategory.new
oc.is_desired_work?= #cv.desired_occupations.include?(occupation)
oc.is_work_experience?= #cv.past_occupations.include?(occupation)
oc.occupation = occupation
if !#occupations.include?(oc)
#occupations << oc
else
obj = #occupations.select(oc)
obj.score += 1
obj.occupations= (obj.occupations & oc).uniq
end
end
=begin
all_occupations = #cv.desired_occupations.all(:include => :labels) + #cv.past_occupations.all(:include => :labels)
all_occupations.each do |occupation|
section = []
section << "Desired Work" if #cv.desired_occupations.include? occupation
section << "Work experience" if #cv.past_occupations.include? occupation
unless (array = #occupations.assoc(occupation)).blank?
array[1]+= 1
array[2] = (array[2] & section).uniq
else
#occupations << [occupation, 1, section, []]
end
end
=end
end
# Prepares the occupation skills of the CV by looping over all the stored
# occupations and retrieving the skills for them and storing them in the
# skills array.
def prepare_occupation_skills
# Loop over all the OccupationCategory objects currently present in the Categorizer.
#occupations.each do |oc|
# For each OccupationCategory object, retrieve all the associated skills, and
# include their label as well.
oc.occupation.skills.all(:include => :labels).each do |skill|
# Get the label of the current concept we're working with.
label = oc.occupation.concept.label(#language).value
# Check if the #skills array already contains a SkillCategory object with the
# skill we're currently checking.
if (sc = #skills.select{|scs| scs.skill == skill}).blank?
# The skill was not found, so create a new entry with the SkillCategory class and set the
# correct values for the various properties
sc = SkillCategory.new
sc.labels << label
sc.score= 1
sc.is_occupation_skill? = true
sc.skill= skill
else
# The skill was found in one of the SkillCategory objects. So we update the score by
# 1 and store the label of the current concept, unless that label is already present.
sc.labels << label unless sc.labels.include?(label)
sc.is_occupation_skill?= true
sc.score+= 1
end
end
end
=begin
#occupations.each do |array|
array[0].skills.all(:include => :labels).each do |skill|
unless (skill_array = #skills.assoc skill).blank?
label = array[0].concept.label(#language).value
skill_array[1]+= 1
skill_array[3] << label unless skill_array[3].include? label
else
#skills << [skill, 1, [], [array[0].concept.label(#language).value]]
end
end
end
=end
end
# Prepares the education skills by checking the CV and adding them to the
# skills array
def prepare_education_skills
# Loop over all the educational skills that are currently associated to the CV.
#cv.educational_skills.all(:include => :labels).each do |skill|
# Check if the #skills array already contains a SkillCategory object with the
# skill we're currently checking.
if (sc = #skills.select{|scs| scs.skill == skill}).blank?
# The skill was not found, so create a new entry with the SkillCategory class and set the
# correct values for the various properties
sc = SkillCategory.new
sc.labels << 'Education skills' unless sc.labels.include?('Education skills')
sc.score= 1
sc.is_educational_skill?= true
sc.skill= skill
else
# The skill was found in one of the SkillCategory objects. So we update the score by
# 1 and store the label of the current concept, unless that label is already present.
sc.labels << 'Education skills' unless sc.labels.include?('Education skills')
sc.is_educational_skill?= true
sc.score+= 1
end
end
=begin
#cv.educational_skills.all(:include => :labels).each do |skill|
unless (array = #skills.assoc skill).blank?
array[1]+= 1
array[3] << 'Education skills' unless array[3].include? 'Education skills'
else
#skills << [skill, 1, ['Education skills'], []]
end
end
=end
end
# Returns all uniq skills with their score and section found.
# array structure for each element
# - 0 : the skill object
# - 1 : the score for the skill
# - 2 : CV location of the skill
# - 3 : ESCO group of the skill
def skills
#skills
end
# Returns all uniq occupations with their score and section found.
# array structure for each element
# - 0 : the occupation object
# - 1 : the score for the occupation
# - 2 : the CV location of the occupation
# - 3 : empty array for occupations
def occupations
#occupations
end
end
When browsing to the relevant view in the application, i'm receiving the following error message from the server:
/home/arne.de.herdt/RubymineProjects/ESCO/app/models/cv_categorizer.rb:21:
syntax error, unexpected tIVAR, expecting kEND
oc.is_desired_work?= #cv.desired_occupations.include?(...
^
/home/arne.de.herdt/RubymineProjects/ESCO/app/models/cv_categorizer.rb:22:
syntax error, unexpected tIVAR, expecting kEND ...
oc.is_work_experience?= #cv.past_occupations.include?(occ...
^
/home/arne.de.herdt/RubymineProjects/ESCO/app/models/cv_categorizer.rb:69:
syntax error, unexpected '=', expecting kEND
sc.is_occupation_skill? = true
^
/home/arne.de.herdt/RubymineProjects/ESCO/app/models/cv_categorizer.rb:75:
syntax error, unexpected kTRUE, expecting kEND
/home/arne.de.herdt/RubymineProjects/ESCO/app/models/cv_categorizer.rb:108:
syntax error, unexpected kTRUE, expecting kEND
/home/arne.de.herdt/RubymineProjects/ESCO/app/models/cv_categorizer.rb:114:
syntax error, unexpected kTRUE, expecting kEND
It seems I'm missing something in the CvCategorize class, but I can't find the stuff missing. The IDE is not showing errors such as missing ends or anything.
Remove the question marks on oc.is_desired_work? and oc.is_work_experience? on lines 21 and 22.
ruby allows the question marks in method names, not the variables ( of any kind ) or object attributes.
the ruby way would be to add instance methods to your OccupationCategory class, like so:
class OccupationCategory
def is_desired_work?
...
end
def is_work_experience?
...
end
end
so you could later use it like
oc.is_desired_work?
oc.is_work_experience?