Grape: custom validator for each element of an array - ruby

Is it possible to run a custom validator against each element of array in Grape? I know I can validate whole array with my validator, but I think error messages would be better if I use it for each element.
My parameters look like this:
"conditions": [
{
"field": "interests",
"operator": "any",
"value": ['cars', 'cats']
},
{
"field": "age",
"operator": "gt",
"value": 25
}
]
With requires :conditions, type: Array, valid_conditions: true the validator is run for the whole array. Is it best I can get?

This is totally possible, You can assert the value for specific keys in a response.
assert_equal some_obj[0].first[1], "interests"
Here is this same thing in irb
🇱🇷 Success weeds out the uncommitted ~ irb
2.2.3 :001 > a = [{:field=>"interests", :operator=>"any", :value=>["cars", "cats"]}]
=> [{:field=>"interests", :operator=>"any", :value=>["cars", "cats"]}]
2.2.3 :002 > a[0].first
=> [:field, "interests"]
2.2.3 :003 > a[0].first[1]
=> "interests"
2.2.3 :004 >

Yes it's possible, but you have to use a custom validator.
Here is an example
class Validator < Grape::Validations::Base
def validate_param!(attr_name, params)
unless params[attr_name].each { //your code here }
fail Grape::Exceptions::Validation, params: [#scope.full_name(attr_name)], message: 'your message'
end
end
end
You would then use it like this:
requires :conditions, type: Array, validator: true

Related

How can I iterate over an array of hashes and form new one

I have a call to Companies House API and response I get from API is an array of hashes.
companies = {
"total_results" => 2,
"items" => [{
"title" => "First company",
"date_of_creation" => "2016-11-09",
"company_type" => "ltd",
"company_number" => "10471071323",
"company_status" => "active"
},
{
"title" => "Second company",
"date_of_creation" => "2016-11-09",
"company_type" => "ltd",
"company_number" => "1047107132",
"company_status" => "active"
}]
}
How I can iterate over companies and get a result similar to:
[{
title: "First company",
company_number: "10471071323"
},
{
title: "Second company",
company_number: "1047107132"
}]
You can use map which will iterate through the elements in an array and return a new array:
companies["items"].map do |c|
{
title: c['title'],
company_number: c['company_number']
}
end
=> [
{:title=>"First company", :company_number=>"10471071323"},
{:title=>"Second company", :company_number=>"1047107132"}
]
companies.map { |company| company.slice('title', 'company_number').symbolize_keys }
This should do the trick.
If you're not using Rails (or, more specifically, ActiveSupport), then symbolize_keys won't be available. In this case, you'd have to go for a more standard-Ruby approach:
companies.map do |company|
{ title: company["title"], company_number: company["company_number"] }
end
The answers are totally correct; but you should be made aware that what you’re looking at from companies house is not just an array of hashes - it’s a valid JsonApi response.
You might find your job easier if you’re using a gem which is aware of JsonApi specs, or if you’re just approaching it as that kind of data.
Have a look at the ruby implementations of https://jsonapi.org/implementations/
Or ActiveModelSerializer for ways to not only reform your hashes but deserialise this very structured data into ruby objects.
But like I say, if all you’re looking for is a quick way to reform the data as you describe. The above answers are perfect.

How to use a string description to access data from a hash-within-hash structure?

I have the following:
data_spec['data'] = "some.awesome.values"
data_path = ""
data_spec['data'].split('.').each do |level|
data_path = "#{data_path}['#{level}']"
end
data = "site.data#{data_path}"
At this point, data equals a string: "site.data['some']['awesome']['values']"
What I need help with is using the string to get the value of: site.data['some']['awesome']['values']
site.data has the following value:
{
"some" => {
"awesome" => {
"values" => [
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
}
}
}
Any help is greatly appreciated. Thanks!
You could do as tadman suggested and use site.data.dig('some', 'awesome', values') if you are using ruby 2.3.0 (which is awesome and I didn't even know existed). This is probably your best choice. But if you really want to write the code yourself read below.
You were on the right track, the best way to do this is:
data_spec['data'] = "some.awesome.values"
data = nil
data_spec['data'].split('.').each do |level|
if data.nil?
data = site.data[level]
else
data = data[level]
end
end
To understand why this works first you need to understand that site.data['some']['awesome']['values'] is the same as saying: first get some then inside that get awesome then inside that get values. So our first step is retrieving the some. Since we don't have that first level yet we get it from site.data and save it to a variable data. Once we have that we just get each level after that from data and save it to data, allowing us to get deeper and deeper into the hash.
So using your example data would initally look like this:
{"awesome" => {
"values" => [
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
}
}
Then this:
{"values" => [
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
}
and finally output like this:
[
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
If you're receiving a string like 'x.y.z' and need to navigate a nested hash, Ruby 2.3.0 includes the dig method:
spec = "some.awesome.values"
data = {
"some" => {
"awesome" => {
"values" => [
'a','b','c'
]
}
}
}
data.dig(*spec.split('.'))
# => ["a", "b", "c"]
If you don't have Ruby 2.3.0 and upgrading isn't an option you can just patch it in for now:
class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
I wrote something that does exactly this. Feel free to take any information of value from it or steal it! :)
https://github.com/keithrbennett/trick_bag/blob/master/lib/trick_bag/collections/collection_access.rb
Check out the unit tests to see how to use it:
https://github.com/keithrbennett/trick_bag/blob/master/spec/trick_bag/collections/collection_access_spec.rb
There's an accessor method that returns a lambda. Since lambdas can be called using the [] operator (method, really), you can get such a lambda and access arbitrary numbers of levels:
accessor['hostname.ip_addresses.0']
or, in your case:
require 'trick_bag'
accessor = TrickBag::CollectionsAccess.accessor(site.data)
do_something_with(accessor['some.awesome.values'])
What you are looking for is something generally looked down upon and for good reasons. But here you go - it's called eval:
binding.eval data

Converting a json string to hashie mash

I have a web service that returns a json in the following format:
[
{
"key": "linux.ubuntu.ip",
"value": "10.10.10.10"
},
{
"key": "linux.ubuntu.hostname",
"value": "stageubuntu"
}
]
I have a ruby code that makes a call to this service and gets the json. Deep in this code, there is a variable configure of type Hashie::Mash.
I want to achieve this:
configure.linux.ubuntu.ip = 10.10.10.10 [Hashie::Mash]
configure.linux.ubuntu.hostname = stageubuntu [Hashie::Mash]
Could anybody tell me if it is possible to achieve this (w.r.t to the json output that I have)? If so, what is the best method to do it?
To get a JSON string to a Hashie::Mash object you can simply do:
require 'json'
require 'hashie'
json_str = '{ "foo": "bar" }'
ruby_hash = JSON.parse(json_str)
Hashie::Mash.new(ruby_hash)
For this specific problem, though not ideal (but we all have our unique use cases) you're needing to parse to an array, then extract the 'key's into nested Hashie::Mash objects of some unknown depth.
require 'json'
require 'hashie'
json_str = <<-JSON
[
{
"key": "linux.ubuntu.ip",
"value": "10.10.10.10"
},
{
"key": "linux.ubuntu.hostname",
"value": "stageubuntu"
}
]
JSON
parsed_arr = JSON.parse(json_str)
#=> [{"key"=>"linux.ubuntu.ip", "value"=>"10.10.10.10"}, {"key"=>"linux.ubuntu.hostname", "value"=>"stageubuntu"}]
configure = parsed_arr.map do |parsed_hash|
method_chain = parsed_hash['key'].split('.')
init_value = Hashie::Mash.new(method_chain.pop => parsed_hash['value'])
method_chain.reverse.inject(init_value) do |ret_value, method_name|
Hashie::Mash.new(method_name => ret_value)
end
end.inject(:merge) # <-- hashie is allows you to perform a deep merge into a single object here.
# you can now do
configure.linux.ubuntu.ip = 10.10.10.10 [Hashie::Mash]
#=> "10.10.10.10"
configure.linux.ubuntu.hostname
#=> "stageubuntu"

Assert two json content by ignoring the order in cucumber

I am comparing two json content or objects in ruby+cucumber like this
but when i compare, it does not ignores the order of content if it varies. I know this statement compares as two strings. So is there anyway i can compare two json objects by ignoring its order sequence?
expect(#act_resp_excl_key).to eq(exp_data_excl_key)
Adding little more information with above details. i have two json document like below.
json1 = {
"entries" = > [{
"doingBusinessAsName" = > "KROGER FOODS",
"legalName" = > "Kroger-Corps"
}
]
}
json2 = {
"entries" = > [{
"legalName" = > "Kroger-Corps"
"doingBusinessAsName" = > "KROGER FOODS",
}
]
}
When i compare these two json in ruby+cucumber, i get the result as failure. But logically it is same and i should get pass. I use the above comparison statement to validate two jsons.
#tgf,
I used the statement which you specified, but still my comparison fails. could you please help me what could be the issue?
expect(JSON.parse(#act_resp_excl_key)).to eq JSON.parse(exp_data_excl_key)
If the data is simply JSON in a string (please post example of data) you can just parse it to a ruby hash and compare that.
require 'json'
JSON.parse(#act_resp_excl_key).class => Hash
Then assert the two hashes are equal:
expect(JSON.parse(#act_resp_excl_key)).to eq JSON.parse(exp_data_excl_key)
This works even if the order is different.
Your commas are not in the correct locations for json2.
> json1 = { "entries" => [{ "doingBusinessAsName" => "KROGER FOODS", "legalName" => "Kroger-Corps" } ] }
=> {"entries"=>[{"doingBusinessAsName"=>"KROGER FOODS", "legalName"=>"Kroger-Corps"}]}
> json2 = { "entries" => [{ "legalName" => "Kroger-Corps", "doingBusinessAsName" => "KROGER FOODS" } ] }
=> {"entries"=>[{"legalName"=>"Kroger-Corps", "doingBusinessAsName"=>"KROGER FOODS"}]}
> json1==json2
=> true

How to validate a complete form with RSpec and Capybara?

I'm writing a request test with RSpec and Capybara. I have a hash that maps form field names to expected values.
How can I check easily that each form field has the expected value?
So far, I'm doing this, but it's complex and unmaintainable. I'm also considering only two kind of input controls in this case (select boxes and the rest):
expected_data = {
"address" => "Fake st 123",
"city" => "Somewhere",
"email" => "whoknows#example.com",
"gender" => "Male",
"state" => "FL",
}
select_boxes = ["gender", "state"]
# check for the select boxes
expected_data.select {|k,v| select_boxes.include?(k)}.each do |name, expected_value|
page.has_select?(name, :selected_value => expected_value).should == true
end
# check for the input fields
expected_data.reject {|k,v| select_boxes.include?(k)}.values.each do |expected_value|
page.should have_css("input[value=\"#{expected_value}\"]")
end
Is there a gem or something to do this in one line?
I find the following far more maintainable:
describe "form" do
subject {page}
before { visit "/path/to/form" }
it { should have_field("address", :with => "Fake st 123") }
it { should have_select("gender", :selected => "Male") }
# And so on ...
end

Resources