What kind of ruby method call is Array(x) - ruby-on-rails

What is the meaning, and where is the Ruby documentation for the syntax of:
Array(phrases)
which I found browsing the Rails source here:
# File actionpack/lib/action_view/helpers/text_helper.rb, line 109
...
119: match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
I thought that Array.new would normally be used to create an array, so something different must be going on here. BTW from the context around this code, the phrases variable can be either a string or an array of strings.

It's most likely the Kernel#Array method, see here. It's slightly different than Array.new; it's more of a cast into an array. (It tries to_ary and to_a.)

Array(x) appears to act exactly the same as x.to_a.
#Brian is right - it's a method of Kernel. Pickaxe says:
Array( arg ) -> anArray
Returns arg .to_a.
Array(1..5) ยป [1, 2, 3, 4, 5]

It's the Kernel#Array method, as others have already stated.
But the Ruby documentation does not give credit to this method's usefulness in simplifying your code. Also it does not tell you that objects which don't have a to_ary or a to_a method are encapsulated in an array.
Array([1,2,3]) -> [1,2,3]
Array(1..3) -> [1,2,3]
Array({ a: 1, b: 2 }) -> [[:a, 1],[:b,2]]
Array("Hello World") -> ["Hello World"]
Array(1) -> [1]
All these features of Kernel#Array allow you to handle typical corner cases with parameters in one single line.
See this code, which is a typical situation in many APIs or DSLs:
# data can be nil, a single value or an array
def handle(data)
data ||= Array.new #Case 1: Data is nil
data = [data] unless data.is_a?(Array) #Case 2: Data is a single value
data.each { |d| ... }
end
This can be simplified by using Kernel#Array:
def handle(data)
Array(data).each { |d| ... }
end
Of course one has to be careful with providing different types for the data parameter, because the to_ary/to_a methods might or might not give you what you expect.

Related

Rails - Accessing JSON members

I am new to Rails, and working with some JSON, and not sure how to get to the data as the examples below:
1) If i were to use JSON.parse(response)['Response']['test']['data']['123456'], i will need to parse another response for 123457, is there a better way to loop through all the objects in data?
2) base on the membershipId, identify the top level object, ie data.
"test": {
"data": {
"123456": {
"membershipId": "321321312",
"membershipType": a,
},
"123457": {
"membershipId": "321321312",
"membershipType": a,
},
}
JSON.parse(response)['Response']['test']['data'].each do |key, object|
puts key
puts object['membershipID']
...
end
To select the data record associated with a particular membership
match_membership = '321321312'
member = JSON.parse(response)['Response']['test']['data'].select |_key, object|
object['membershipID'] == match_membership
end
puts member.key
=> 123456
For 1:
Assumption:
By you saying "need to parse another response", you were doing something like below:
# bad code: because you are parsing `response` multiple times
JSON.parse(response)['Response']['test']['data']['123456']
JSON.parse(response)['Response']['test']['data']['123457']
then simply:
Solution 1:
If you are gonna be accessing 2+ level deep hash values for just maybe 2 or 3 times,
response_hash = JSON.parse(response)
response_hash['Response']['test']['data']['123456']
response_hash['Response']['test']['data']['123457']
Solution 2:
If you are gonna be accessing 2+ level deep hash values for loooooots of times,
response_hash = JSON.parse(response)
response_hash_response_test_data = response_hash['Response']['test']['data']
response_hash_response_test_data['123456']
response_hash_response_test_data['123457']
response_hash_response_test_data['123458']
response_hash_response_test_data['123459']
response_hash_response_test_data['123460']
# ...
Solution 2 is better than Solution 1 because it saves repetitive method calls for Hash#[] which is the "getter" method each time you do like ...['test'] then ['data'] then ['123456'], and so is better-off doing Solution 2 which you store the nested-level of the hash into a variable (this does not duplicate the values in-memory!). Plus it's more readable this way.

Clone constant of hash into new variable without mutating constant on update with .each block?

I'm struggling with something. I've abstracted my code out to be as simple as possible, yet I still don't understand why it's having this behaviour.
I'm creating a constant consisting of a set of key-value pairs and freezing it. I'm then using the .dup method to copy the hash into a new variable.
However, when I iterate over an array and try to store it in the (previously empty) array in the new variable, it not only updates the new variable, but also the original constant. This only seems to be the case with the .each method - if I pass the new values directly as a new array, it works without updating the constant.
My abstracted code is below:
CONFIG_VALUES = { results: [], loop_count: 0 }.freeze
the_results = ["foo", "bar"]
abc = CONFIG_VALUES.dup
the_results.each do |res|
abc[:results] << res
end
abc
#=> {:results=>["foo", "bar"], :loop_count=>0}
CONFIG_VALUES
#=> {:results=>["foo", "bar"], :loop_count=>0}
Hash#dup method isn't recursive. Anyway, if you use Ruby on Rails, and I think you do since you tagged it, you can use #deep_dup method: http://api.rubyonrails.org/classes/Hash.html#method-i-deep_dup
It's an ActiveSupport method, so you could just use the gem in case you aren't using Ruby on Rails.
You can achieve the desired result with:
CONFIG_VALUES = { results: [], loop_count: 0 }.freeze
the_results = %w[foo bar]
abc = CONFIG_VALUES.merge(results: the_results)
abc
#=> {:results=>["foo", "bar"], :loop_count=>0}
CONFIG_VALUES
#=> {:results=>[], :loop_count=>0}
As I understand it, this works because #merge does not mutate CONFIG_VALUES and you are essentially creating an entirely new set of objects.

Update Each Array-Object Value in Rails

Basically I want to update each table column for a Model in Rails 5.
str = "abc---def"
str.split('---').map do |a|
Foo.where(product_id:1).update_all(bar: a)
end
Old object would be like:
[
[0] { product_id: 1,
...,
bar: "xxx",
...
},
[1] { product_id: 1,
...,
bar: "xxx",
...
}
]
New should be like:
[
[0] { product_id: 1,
...,
bar: "abc",
...
},
[1] { product_id: 1,
...,
bar: "def",
...
}
]
But what I got is bar: "def" for each. Is there a clean method in rails to achieve what I want? update_attributes gives an error.
Is the title name correct?
First of all let's get started from some basics.
You want to update multiple rows and want to set different value for each row. So it cannot be done in single query like you are doing. So you need to loop through the Foo objects and set each one separately.
So let's assume
str = "abc---def---ghi---jkl"
tokens = str.split('---')
foos_to_update = Foo.where(product_id: 1) #Let's assume it will return 4 or lesser records. (otherwise you need to tell what do you wanna do if it returns more then `tokens`)
foos_to_update.each_with_index {|foo,i| foo.update(bar: tokens[i])}
The last line is looping through returned objects and setting the bar value for each object.
First of all, using Foo.where(id:1).update_all to update a single record may work, but is non-idiomatic. It's better to use Foo.find_by(id: 1).update. For getting single records, I prefer to use find_by instead of find because it returns nil instead of raising NotFound errors, but that's a personal preference.
Second, the way you're using update_all(bar: a) is giving you unexpected results. In a map block, the returned value becomes part of the resulting array. update_all doesn't return the record which were changed. It returns an integer showing the count of records which were changed. Similarly, update doesn't return the record. It returns true or false` depending on if the validations passed.
Tying together these concepts, the following code can be written:
str = "abc---def"
str.split('---').map do |a|
foo = Foo.find_by(id:1)
foo&.update(bar: a)
foo
end
# note that you could instead write `foo.update(bar: a)` if you
# don't want to use the safe navigation operator
Or another way to write it which does the same thing:
str = "abc---def"
str.split('---').map do |a|
Foo.find_by(id:1)&.tap { |foo| foo.update(bar: a) }
end
Note that in these examples I'm using the safe navigation operator which is in Ruby versions newer than 2.3. It helps prevent NoMethodError on nil objects, but isn't really necessary.

Is there a safe way to Eval In ruby? Or a better way to do this?

When a user uses my application, at one point they will get an array of arrays, that looks like this:
results = [["value",25], ["value2",30]...]
The sub arrays could be larger, and will be in a similar format. I want to allow my users to write their own custom transform function that will take an array of arrays, and return either an array of arrays, a string, or a number. A function should look like this:
def user_transform_function(array_of_arrays)
# eval users code, only let them touch the array of arrays
end
Is there a safe way to sandbox this function and eval so a user could not try and execute malicious code? For example, no web callouts, not database callouts, and so on.
First, if you will use eval, it will never be safe. You can at least have a look in the direction of taint method.
What I would recommend is creating your own DSL for that. There is a great framework in Ruby: http://treetop.rubyforge.org/index.html. Of course, it will require some effort from your side, but from the user prospective I think it could be even better.
WARNING: I can not guarantee that this is truly safe!
You might be able to run it as a separate process and use ruby $SAFE, however this does not guarantee that what you get is safe, but it makes it harder to mess things up.
What you then would do is something like this:
script = "arr.map{|e| e+2}" #from the user.
require "json"
array = [1, 2, 3, 4]
begin
results = IO.popen("ruby -e 'require \"json\"; $SAFE=3; arr = JSON.parse(ARGV[0]); puts (#{script}).to_json' #{array.to_json}") do |io|
io.read
end
rescue Exception => e
puts "Ohh, good Sir/Mam, your script caused an error."
end
if results.include?("Insecure operation")
puts "Ohh, good Sir/Mam, you cannot do such a thing"
else
begin
a = JSON.parse(results)
results = a
rescue Exception => e
puts "Ohh, good Sir/Mam, something is wrong with the results."
puts results
end
end
conquer_the_world(results) if results.is_a?(Array)
do_not_conquer_the_world(results) unless results.is_a?(Array)
OR
You could do this, it appears:
def evaluate_user_script(script)
Thread.start {
$SAFE = 4
eval(script)
}
end
But again: I do not know how to get the data out of there.

Extracting JSON objects from JSON string

I want to break down a JSON string into smaller objects. I have two servers, one acting as the web-app interface to the whole application and the other is a repository/database.
I'm able to retrieve information from the repository to the web-app as JSON, but after that I don't know how to return it.
Here's a sample of the JSON being returned:
{"respPages":[{"page":{"page_url":"http://www.google.com/","created_at":"2011-08-10T11:00:19Z","website_id":1,"updated_at":"2011-08-10T11:00:19Z","id":1}},{"page":{"page_url":"http://www.blank.com/services/content_services/","created_at":"2011-08-10T11:02:46Z","website_id":1,"updated_at":"2011-08-10T11:02:46Z","id":2}}],"respSite":{"website":{"created_at":"2011-08-10T11:00:19Z","website_id":null,"updated_at":"2011-08-10T11:00:19Z","website_url":null,"id":1}},"respElementTypes":[{"element_type":{"created_at":"2011-08-10T11:00:19Z","updated_at":"2011-08-10T11:00:19Z","id":1,"tag_name":"head"}},
There are four tags in the JSON:
page
website
elementType
elementData
I would like to create four arrays and populate them with the object that matches these tags.
I would image the code is something like this:
#Get the json from repo using net/http
uri = URI.parse("http://127.0.0.1:3007/repository/infoid/1.json")
http = Net::HTTP.new(uri.host, uri.port)
response = http.request(Net::HTTP::Get.new(uri.request_uri))
#x = response.to_hash
#pages = Array.new
#websites= Array.new
#elementDatas = Array.new
#elementTypes = Array.new
#enter code here`#For every bit of the hash, find out what it is and allocate it accordingly
#x.each_with_index do |e,index|
if e.tagName == pages #Getting real javascripty here. There must be someway to check the tag or title of the element
#pages[index]=e
end
My goal for the returned value is to have four arrays, each containing different types of objects:
#pagesArray[1]
should contain the first occurrence of a page object in the JSON string. Then do the same for the other ones.
Of course I'd need to break down the object further but once I can break down the top level and categorize them, then I can go deeper.
In the JSON there are already tag titles respPages and respWebsites which group all the objects.
How do I turn JSON back into objects in Ruby and reference them using something like the tag name?
You should be able to decode anything in JSON format using the standard JSON library:
JSON.load(...)
It will throw exceptions on malformed JSON data, so be sure to test it thoroughly and make sure it can handle all the important cases.
If you're trying to navigate the structure of the JSON itself, you probably need to write a series of recursive methods that handle each case along the way. A good pattern to start with is this:
#data.each do |key, value|
case (key)
when 'someKey'
handle_some_key(value)
when 'otherKey'
handle_other_key(value)
end
end
You can either break out the behavior into methods as in this example, or inline it if the logic is fairly straightforward.
As a note, an alternative to Array.new is simply [ ] as it is in JavaScript. For example:
#pages = [ ]
You'll see this used frequently in most Ruby examples. The alternative to Hash.new is { }.
The following works:
json = {"respPages"=>[{"page"=>{"page_url"=>"http://www.google.com", "created_at"=>"2011-08-10T11:00:19Z", "website_id"=>1, "updated_at"=>"2011-08-10T11:00:19Z", "id"=>1}}, {"page"=>{"page_url"=>"http://www.blank.com/services/content_services/", "created_at"=>"2011-08-10T11:02:46Z", "website_id"=>1, "updated_at"=>"2011-08-10T11:02:46Z", "id"=>2}}],
"respSite"=>{"website"=>{"created_at"=>"2011-08-10T11:00:19Z", "website_id"=>nil, "updated_at"=>"2011-08-10T11:00:19Z", "website_url"=>nil, "id"=>1}},
"respElementTypes"=>[{"element_type"=>{"created_at"=>"2011-08-10T11:00:19Z", "updated_at"=>"2011-08-10T11:00:19Z", "id"=>1, "tag_name"=>"head"}}]}
#respPages, #respSite, #respElementTypes = [], [], []
json.each do |key_category, group_category|
group_category.each do |hash|
if group_category.is_a? Array
eval("##{key_category}") << hash.values.first
elsif group_category.is_a? Hash
eval("##{key_category}") << hash[1]
end
end
end
there weren't any respData in your sample but you've got the idea.

Resources