I'm trying to complete this Codewars Challenge and I'm confused as to where I'm going wrong. Could someone please give me a hand?
The question provides a "database" of translations for Welcome, and the instructions say:
Think of a way to store the languages as a database (eg an object). The languages are listed below so you can copy and paste!
Write a 'welcome' function that takes a parameter 'language' (always a string), and returns a greeting - if you have it in your database. It should default to English if the language is not in the database, or in the event of an invalid input.
My attempt:
def greet(language)
greeting = { 'english'=>'Welcome',
'czech'=>'Vitejte',
'danish'=>'Velkomst',
'dutch'=>'Welkom',
'estonian'=>'Tere tulemast',
'finnish'=>'Tervetuloa',
'flemish'=>'Welgekomen',
'french'=>'Bienvenue',
'german'=>'Willkommen',
'irish'=>'Failte',
'italian'=>'Benvenuto',
'latvian'=>'Gaidits',
'lithuanian'=>'Laukiamas',
'polish'=>'Witamy',
'spanish'=>'Bienvenido',
'swedish'=>'Valkommen',
'welsh'=>'Croeso'
}
greeting.key?(language) ? greeting.each { |k, v| return v if language == k } : 'IP_ADDRESS_INVALID'
end
To my eyes when I run my code through the IDE it seems to be working as per request but I guess I must be wrong somehow.
It's telling me it :
Expected: "Laukiamas", instead got: "Welcome"
But when I type:
p greet("lithuanian")
I get Laukiamas.
You can provide you greeting hash with a default value. It is as simple as
greeting.default = "Welcome"
This enhanced hash does all the work for you. Just look up the key; when it is not there you'll get "Welcome".
Preface
First of all, please don't post links to exercises or homework questions. Quote them in your original question to avoid link rot or additional create work for people trying to help you out.
Understanding the Problem Defined by the Linked Question
Secondly, you're misunderstanding the core question. The requirement is basically to return the Hash value for a given language key if the key exists in the Hash. If it doesn't, then return the value of the 'english' key instead. Implicit in the exercise is to understand the various types of improper inputs that would fail to find a matching key; the solution below addresses most of them, and will work even if your Ruby has frozen strings enabled.
A Working Solution
There are lots of ways to do this, but here's a simple example that will handle invalid keys, nil as a language argument, and abstract away capitalization as a potential issue.
DEFAULT_LANG = 'english'
TRANSLATIONS = {
'english' => 'Welcome',
'czech' => 'Vitejte',
'danish' => 'Velkomst',
'dutch' => 'Welkom',
'estonian' => 'Tere tulemast',
'finnish' => 'Tervetuloa',
'flemish' => 'Welgekomen',
'french' => 'Bienvenue',
'german' => 'Willkommen',
'irish' => 'Failte',
'italian' => 'Benvenuto',
'latvian' => 'Gaidits',
'lithuanian' => 'Laukiamas',
'polish' => 'Witamy',
'spanish' => 'Bienvenido',
'swedish' => 'Valkommen',
'welsh' => 'Croeso'
}
# Return a translation of "Welcome" into the language
# passed as an argument.
#
# #param language [String, #to_s] any object that can
# be coerced into a String, and therefore to
# String#downcase
# #return [String] a translation of "Welcome" or the
# string-literal +Welcome+ if no translation found
def greet language
language = language.to_s.downcase
TRANSLATIONS.fetch language, TRANSLATIONS[DEFAULT_LANG]
end
# Everything in the following Array of examples except
# +Spanish+ should return the Hash value for +english+.
['Spanish', 'EspaƱol', 123, nil].map { greet(_1) }
This will correctly return:
#=> ["Bienvenido", "Welcome", "Welcome", "Welcome"]
because only Spanish (when lower-cased) will match any of the keys currently defined in the TRANSLATIONS Hash. All the rest will use the default value defined for the exercise.
Test Results
Since there are some RSpec tests included with the linked question:
describe "Welcome! Translation" do
it "should translate input" do
Test.assert_equals(greet('english'), 'Welcome', "It didn't work out this time, keep trying!");
Test.assert_equals(greet('dutch'), 'Welkom', "It didn't work out this time, keep trying!");
Test.assert_equals(greet('IP_ADDRESS_INVALID'), 'Welcome', "It didn't work out this time, keep trying!")
end
end
The code provided not only passes the provided tests, but it also passes a number of other edge cases not defined in the unit tests. When run against the defined tests, the code above passes cleanly:
If this is homework, then you might want to create additional tests to cover all the various edge cases. You might also choose to refactor to less idiomatic code if you want more explanatory variables, more explicit intermediate conversions, or more explicit key handling. The point of good code is to be readable, so be as explicit in your code and as thorough in your tests as you need to be in order to make debugging easier.
Related
I'm trying to create a validation for a predetermined list of valid brands as part of an ETL pipeline. My validation requires case insensitivity, as some brands are compound words or abbreviations that are insignificant.
I created a custom predicate, but I cannot figure out how to generate the appropriate error message.
I read the error messages doc, but am having a hard time interpreting:
How to build the syntax for my custom predicate?
Can I apply the messages in my schema class directly, without referencing an external .yml file? I looked here and it seems like it's not as straightforward as I'd hoped.
Below I've given code that represents what I have tried using both built-in predicates, and a custom one, each with their own issues. If there is a better way to compose a rule that achieves the same goal, I'd love to learn it.
require 'dry/validation'
CaseSensitiveSchema = Dry::Validation.Schema do
BRANDS = %w(several hundred valid brands)
# :included_in? from https://dry-rb.org/gems/dry-validation/basics/built-in-predicates/
required(:brand).value(included_in?: BRANDS)
end
CaseInsensitiveSchema = Dry::Validation.Schema do
BRANDS = %w(several hundred valid brands)
configure do
def in_brand_list?(value)
BRANDS.include? value.downcase
end
end
required(:brand).value(:in_brand_list?)
end
# A valid string if case insensitive
valid_product = {brand: 'Valid'}
CaseSensitiveSchema.call(valid_product).errors
# => {:brand=>["must be one of: here, are, some, valid, brands"]} # This message will be ridiculous when the full brand list is applied
CaseInsensitiveSchema.call(valid_product).errors
# => {} # Good!
invalid_product = {brand: 'Junk'}
CaseSensitiveSchema.call(invalid_product).errors
# => {:brand=>["must be one of: several, hundred, valid, brands"]} # Good... (Except this error message will contain the entire brand list!!!)
CaseInsensitiveSchema.call(invalid_product).errors
# => Dry::Validation::MissingMessageError: message for in_brand_list? was not found
# => from .. /gems/2.5.0/gems/dry-validation-0.12.2/lib/dry/validation/message_compiler.rb:116:in `visit_predicate'
The correct way to reference my error message was to reference the predicate method. No need to worry about arg, value, etc.
en:
errors:
in_brand_list?: "must be in the master brands list"
Additionally, I was able to load this error message without a separate .yml by doing this:
CaseInsensitiveSchema = Dry::Validation.Schema do
BRANDS = %w(several hundred valid brands)
configure do
def in_brand_list?(value)
BRANDS.include? value.downcase
end
def self.messages
super.merge({en: {errors: {in_brand_list?: "must be in the master brand list"}}})
end
end
required(:brand).value(:in_brand_list?)
end
I'd still love to see other implementations, specifically for a generic case-insensitive predicate. Many people say dry-rb is fantastically organized, but I find it hard to follow.
Apologies in advance for what I think might be a stupid question. I promise I have searched, but I'm not sure if I've searched for the correct things. I'm a very new programmer, Ruby is my first serious language, and I taught myself it over the past few months. I've also never asked a question on StackOverflow, so hopefully this is acceptably clear and meets the other requirements for question-asking. Here is the question:
I have a branching method that calls various different APIs and various different URLs within them, based upon the values passed to the method. As is, it looks like:
if api == x
url = "http://url.x.com/api/#{#variable}"
elsif api == y
url = "http://url.y.com/api/public/#{#var1}_#{#var2}/#{#variable}"
etc.
The url's being called are different for each operation, as are the necessary variables. The variables used in the requests are being created as instance variables at the beginning of the method. The possible values of the instance variables are stored in a large hash or are being passed into the method by the call itself. The hash is structured like:
$globalhash =
{
"api_1" =>
{
"call_type_1" => {"info_1" => "", "info_2" => ""},
"call_type_2" => {"info_1" => "", "info_2" => ""}
},
"api_2" =>
{
"call_type_1" => {"info_1" => "", "info_2" => ""},
"call_type_2" => {"info_1" => "", "info_2" => ""}
}
}
The problem I have is that this branching section goes on for a long time in the code--partially because I've done it suboptimally, I'm sure. I'm also sure that my code would be much more efficient if the branching section didn't exist. Ideally, instead of the branching section, I'd like to make this happen:
url = $globalhash[#api][#call_type]["url"]
The value that pulls would be a URL specific to the call type and the api--the address, formatting, and various other differences included. So some values would be:
http://api.x.com/#var1_#var2/#variable
http://api.y.com/public/#variable
and so on. So the structures vary, and I need to access the instance variables stored within the method call, but I need to do so dynamically. The issue I've had is that every way I've tried to implement this results in the values of the instance variables in the strings for "url" being set when $globalhash is read as the program begins to run, with them all being nil. I need it to check the variable when the request is being made, and not before, basically. I have a similar (same?) issue with setting the post_data for the request--if I could find a way to set it in the hash, I'd have cleaner code that runs faster. The post_data is as:
post_data = {'value' => #var1, 'value2' => #var2, 'value3' => #var3}
and so on. The post_data is different for each API and for each call_type; the values are different as are the requested variables for them. I'd like to implement a set of key-value pairs in the hash that look vaguely like:
$globalhash = {"api_1" => {"call_type_1" => {"url" => "http://api.x.com/#{#variable}", "post_data" => "{'value' => #var1, 'value2' => #var2, etc.}"}}}
Except, of course, it would need to work--the variables it needs to reference are nil when $globalhash is being read. I'm not sure how best to solve this.
The program works as-is, but I have a very derpy-looking ~80 lines of branching code that figures out the structure of the URL and the post_data, and then requests it.
The only solution I've considered is creating an instance hash within the method to replace the branching code, that only gets created after declaring the instance variables. However, I'm afraid that would create a similar problem to the current one, of it being inefficient to create a huge new hash every time.
Thanks for reading, and I appreciate your help. I hope the pseudo-code is acceptable, I found it the best way to explain my question.
EDIT: Found a solution. Code:
url = eval "\"#{$globalhash["api_1"]["call_type_1"]["url"]}\""
["url"] references 'http://api.x.com/#{#variable}', single quotes prevent interpolation within the hash, eval function puts it in double quotes and string interpolation collects the instance variable at the time it is called. It's an ugly methodology BUT it does let me greatly shorten my code. Source: https://www.ruby-forum.com/topic/3237101
Instead of creating the hash using the instance variables...
post_data = {'value' => #var1, 'value2' => #var2, 'value3' => #var3}
...you could initialize the hash using strings representing the instance variables.
post_data = {'value' => '#var1', 'value2' => '#var2', 'value3' => '#var3'}
Then when you reference the hash variable, you would wrap the call in an eval.
post_data = {'value' => '#var1', 'value2' => '#var2', 'value3' => '#var3'}
#var2 = 'this is test data'
eval(post_data['value2'])
=> "this is test data"
Similarly I would use placeholders in the url...
url = 'http://api.x.com/[#variable]'
#variable = "cotton_tree"
my_url = url.dup
my_url.sub!(/\[.*?\]/, eval(my_url.match(/\[(.*?)\]/)[1])) while my_url.match(/\[(.*?)\]/)
p my_url
=> "http://api.x.com/cotton_tree"
I am receiving xml-serialised RDF (as part of XMP media descriptions in case that is relevent), and processing in Ruby. I am trying to work with rdf gem, although happy to look at other solutions.
I have managed to load and query the most basic data, but am stuck when trying to build a query for items which contain sequences and bags.
Example XML RDF:
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<rdf:Description rdf:about='' xmlns:dc='http://purl.org/dc/elements/1.1/'>
<dc:date>
<rdf:Seq>
<rdf:li>2013-04-08</rdf:li>
</rdf:Seq>
</dc:date>
</rdf:Description>
</rdf:RDF>
My best attempt at putting together a query:
require 'rdf'
require 'rdf/rdfxml'
require 'rdf/vocab/dc11'
graph = RDF::Graph.load( 'test.rdf' )
date_query = RDF::Query.new( :subject => { RDF::DC11.date => :date } )
results = date_query.execute(graph)
results.map { |result| { result.subject.to_s => result.date.inspect } }
=> [{"test.rdf"=>"#<RDF::Node:0x3fc186b3eef8(_:g70100421177080)>"}]
I get the impression that my results at this stage ("query solutions"?) are a reference to the rdf:Seq container. But I am lost as to how to progress. For the example above, I'd expect to end up, eventually, with an array ["2013-04-08"].
When there is incoming data without the rdf:Seq and rdf:li containers, I am able to extract the strings I want using RDF::Query, following examples at http://rdf.rubyforge.org/RDF/Query.html - unfortunately I cannot find any examples of more complex queries or RDF structures processed in Ruby.
Edit: In addition, when I try to find appropriate methods to use with the RDF::Node object, I cannot see any way to explore any further relations it may have:
results[0].date.methods - Object.methods
=> [:original, :original=, :id, :id=, :node?, :anonymous?, :unlabeled?, :labeled?, :to_sym, :resource?, :constant?, :variable?, :between?, :graph?, :literal?, :statement?, :iri?, :uri?, :valid?, :invalid?, :validate!, :validate, :to_rdf, :inspect!, :type_error, :to_ntriples]
# None of the above leads AFAICS to more data in the graph
I know how to get the same data in xpath (well, at least provided we always get the same paths in the serialisation), but feel it is not the best query language to use in this case (it's my backup plan, however, if it turns out too complex to implement an RDF-query solution)
I think you're correct when saying "my results at this stage ("query solutions"?) are a reference to the rdf:Seq container". RDF/XML is a really horrible serialisation format, instead think of the data as a graph. Here a picture of an RDF:Bag. RDF:Seq works the same and the #students in the example is analogous to the #date in your case.
So to get to the date literal, you need to hop one node further in the graph. I'm not familiar with the syntax of this Ruby library, but something like:
require 'rdf'
require 'rdf/rdfxml'
require 'rdf/vocab/dc11'
graph = RDF::Graph.load( 'test.rdf' )
date_query = RDF::Query.new({
:yourThing => {
RDF::DC11.date => :dateSeq
},
:dateSeq => {
RDF.type => RDF.Seq,
RDF._1 => :dateLiteral
}
})
date_query.execute(graph).each do |solution|
puts "date=#{solution.dateLiteral}"
end
Of course, if you expect the Seq to actually to contain multiple dates (otherwise it wouldn't make sense to have a Seq), you will have to match them with RDF._1 => :dateLiteral1, RDF._2 => :dateLiteral2, RDF._3 => :dateLiteral3 etc.
Or for a more generic solution, match all the properties and objects on the dateSeq with:
:dateSeq => {
:property => :dateLiteral
}
and then filter out the case where :property ends up being RDF:type while :dateLiteral isn't actually the date but RDF:Seq. Maybe the library has also a special method to get all the Seq's contents.
I have a Rails app that has a COUNTRIES list with full country names and abbreviations created inside the Company model. The array for the COUNTRIES list is used for a select tag on the input form to store abbreviations in the DB. See below. VALID_COUNTRIES is used for validations of abbreviations in the DB. FULL_COUNTRIES is used to display the full country name from the abbreviation.
class Company < ActiveRecord::Base
COUNTRIES = [["Afghanistan","AF"],["Aland Islands","AX"],["Albania","AL"],...]
COUNTRIES_TRANSFORM = COUNTRIES.transpose
VALID_COUNTRIES = COUNTRIES_TRANSPOSE[1]
FULL_COUNTRIES = COUNTRIES_TRANSPOSE[0]
validates :country, inclusion: { in: VALID_COUNTRIES, message: "enter a valid country" }
...
end
On the form:
<%= select_tag(:country, options_for_select(Company::COUNTRIES, 'US')) %>
And to convert back the the full country name:
full_country = FULL_COUNTRIES[VALID_COUNTRIES.index(:country)]
This seems like an excellent application for a hash, except the key/value order is wrong. For the select I need:
COUNTRIES = {"Afghanistan" => "AF", "Aland Islands" => "AX", "Albania" => "AL",...}
While to take the abbreviation from the DB and display the full country name I need:
COUNTRIES = {"AF" => "Afghanistan", "AX" => "Aland Islands", "AL" => "Albania",...}
Which is a shame, because COUNTRIES.keys or COUNTRIES.values would give me the validation list (depending on which hash layout is used).
I'm relatively new to Ruby/Rails and am looking for the more Ruby-like way to solve the problem. Here are the questions:
Does the transpose occur only once, and if so, when is it executed?
Is there a way to specify the FULL_ and VALID_ lists that do not require the transpose?
Is there a better or reasonable alternate way to do this? For instance, VALID_COUNTRIES is COUNTRIES[x][1] and FULL_COUNTRIES is COUNTRIES[x][0], but VALID_ must work with the validation.
Is there a way to make a hash work with just one hash rather then one for the select_tag and one for converting the abbreviations in the DB back to full names for display?
1) Does the transpose occur only once, and if so, when is it executed?
Yes at compile time because you are assigning to constants if you want it to be evaluated every time use a lambda
FULL_COUNTRIES = lambda { COUNTRIES_TRANSPOSE[0] }
2) Is there a way to specify the FULL_ and VALID_ lists that do not require the transpose?
Yes use a map or collect (they are the same thing)
VALID_COUNTRIES = COUNTRIES.map &:first
FULL_COUNTRIES = COUNTRIES.map &:last
3) Is there a better or reasonable alternate way to do this? For instance, VALID_COUNTRIES is COUNTRIES[x][1] and FULL_COUNTRIES is COUNTRIES[x][0], but VALID_ must work with the validation.
See Above
4) Is there a way to make the hash work?
Yes I am not sure why a hash isn't working as the rails docs say options_for_select will use hash.to_a.map &:first for the options text and hash.to_a.map &:last for the options value so the first hash you give should be working if you can clarify why it is not I can help you more.
Well, I've finally decided that I'm not crazy. So, that leaves DataMapper.
Here's what I'm doing. I have a model Msrun which has 1 Metric.
tmp = Msrun.first_or_create # I'll skip the boring details
tmp.metric = Metric.first_or_create( {msrun_id: tmp.id}, {metric_input_file: #metricsfile} )
p tmp.metric # => #<Metric #metric_input_file=nil #msrun_id=1>
tmp.metric.metric_input_file = #metricsfile
p tmp.metric # => #<Metric #metric_input_file=#<Pathname:/home/ryanmt/Dropbox/coding/rails/metrics_site/spec/tfiles/single_metric.txt> #msrun_id=1>
So, why doesn't this work? I'm reading http://datamapper.org/docs/create_and_destroy and doing what it shows working. This has been terribly arduous. Thanks for any help.
Update:
I still can't figure out what is going on, but to prove I'm not insane...
puts Metric.all # => []
tmp.metric = Metric.first_or_create( {msrun_id: tmp.id}, {metric_input_file: #metricsfile} )
puts Metric.all # => [] #??????????????
tmp.metric.metric_input_file = #metricsfile
p tmp.metric # => #<Metric #metric_input_file=#<Pathname:/home/ryanmt/Dropbox/coding/rails/metrics_site/spec/tfiles/single_metric.txt> #msrun_id=1>
tmp.metric.save
puts Metric.all # => [#<Metric #metric_input_file=#<Pathname:/home/ryanmt/Dropbox/coding/rails/metrics_site/spec/tfiles/single_metric.txt> #msrun_id=1>]
So, not only is first_or_create not delivering on the behavior I expect by reading the source
def first_or_create(conditions = {}, attributes = {})
first(conditions) || create(conditions.merge(attributes))
end
but it is also not even creating.
I'm probably missing something here (more of those boring details might help) but if the metric exists, it's metric_input_file shouldn't be updated, i.e., it's only set when new. If you're after updating then you can do
.first_or_create(msrun_id: tmp.id).update(metric_input_file: #metricsfile)
Or if not hitting the database twice is relevant, then
m = Metric.first_or_new(msrun_id: tmp.id)
[set..save..assign]
But if it's not being set on new models, I don't see what would cause that from the code posted so far, more..?
[UPDATED]
Based on your new code, I'd say this is "a classic case" of a false DM save. I usually add the following line to an initialization section, e.g., application.rb in Rails.
DataMapper::Model.raise_on_save_failure = true
Unfortunately, the exception raised never tells you why (there's a special place in hell for that choice, right next to people who talk in theaters.) But it's typically one of:
a slightly incorrect association definition
a has/belongs_to that isn't "required: false" and isn't set
putting the wrong datatype into a field, e.g., a string into a decimal
a validation failing
If you want to post your model definitions, the problem may be spottable there.
In addition to the answer above, I've seen this call die (like, literally halt all execution) with no error when I was doing a find_or_create that would have created an object that violated the primary key constraint. This is because the datamapper model was not in sync with the actual database schema.