Better way to form a Ruby hash dynamically? - ruby-on-rails

I need to form a hash dynamically after checking the existence of the values. I always run into this case and form the hash as follows, but I find this very ugly, and wondered if their is a tricky or a prettier way to do this:
Here is how I am forming it:
args = {}
args[:pagination_token] = params[:pagination_token] if params[:pagination_token]
args[:sort_by] = params[:sort_by] if params[:sort_by]
args[:sort_direction] = params[:sort_direction] if params[:sort_direction]
args[:pagination_direction] = params[:pagination_direction] if params[:pagination_direction]
args[:limit] = params[:limit] if params[:limit]
users_list, next_pagination_token, previous_pagination_token = current_application.paginated_users(args)
A better way?

You can use slice like so:
args = params.slice(:pagination_token, :sort_by, :sort_direction, :pagination_direction, :limit)
Edit:
Since the question's subject says "in Ruby", and the above method will work only if your have included ActiveSupport. Which in fact will work in this case as OP is using Rails. For those who wants to do it in Ruby can do:
valid_params = [:pagination_token, :sort_by, :sort_direction, :pagination_direction, :limit]
args = params.select{ |k| valid_params.include? k }

Use ActiveSupport's Hash#slice:
args = params.slice(:pagination_token, :sort_by, :sort_direction, :pagination_direction, :limit)

Here is one more way it can be done:
h = {
pagination_token: 100,
sort_by: 200,
# sort_direction: 300,
pagination_direction: 400,
limit: 500,
some_other_value: 500,
}
keys = [:pagination_token, :sort_by, :sort_direction]
p [keys, h.values_at(*keys)].transpose.to_h.reject {|_,v| v.nil?}
#=> {:pagination_token=>100, :sort_by=>200}

Related

Ruby - Find the ending of a filename within certain parameters

I'm needing to get secondary images for a product (which could have up to 10 images), but having a bit of an issue. Where the issue lies is secondary_images/#{id}_20.jpg. The _20 could be anything from _1 to _11 and _01 to _30. Unfortunately when the images were put into the system, they weren't 100% cohesive with the naming convention. I'd change the names, but there are over 50,000 images. What would be the proper way accomplish this?
*This code does work if the secondary_images/#{id} does end in _20.
def image_url(type = nil)
no_image = type.nil? ? 'no-image-lg.png' : 'no-image.png'
return "//img#{rand(0..9)}.foo.com/#{no_image}" unless has_image?
require 'net/http'
id = sprintf('%07d', master_product_id)
url = if type == 'secondaries'
"//img#{rand(0..9)}.foo.com/product_images/secondary_images/#{id}_20.jpg"
elsif type == 'thumbnail'
"//img#{rand(0..9)}.foo.com/product_images/thumbnails/#{id}.jpg"
else
"//img#{rand(0..9)}.foo.com/product_images/#{id}.jpg"
end
url = URI.parse(URI.encode(url.to_s))
req = Net::HTTP.new(url.host, url.port)
res = req.request_head(url.path)
res.code == '200' ? url.to_s : "//img#{rand(0..9)}.foo.com/#{no_image}"
end
def images
images = {}
images['main'] = image_url
images['thumbnail'] = image_url 'thumbnail'
images['secondaries'] = image_url 'secondaries'
images.to_dot
end
Thanks guys!
It's not pretty, but you can use the following regex to match the corresponding digits.
/.*(([1-9])|(0[1-9])|([1-3]\d))\.jpg\b/
Here's an example:
https://regex101.com/r/gJ2jU7/1
It's really difficult to tell what you want, but perhaps it'll help to know that Ruby includes routines that make it easy to get at the filename in a path. Once you have that it's easy to extract parts of the name since it's string manipulation.
def get_image_num(fname)
File.basename(fname, File.extname(fname)).split('_').last
end
[
'secondary_images/foo_1.jpg',
'secondary_images/foo_11.jpg',
'secondary_images/foo_01.jpg',
'secondary_images/foo_30.jpg',
'secondary_images/foo_20.jpg'
].map{ |s|
get_image_num(s)
}
# => ["1", "11", "01", "30", "20"]
If you need to get both the id value, and the file number, I'd do something like:
def get_image_num(fname)
File.basename(fname, File.extname(fname)).split('_')
end
[
'secondary_images/123_1.jpg',
'secondary_images/123_11.jpg',
'secondary_images/123_01.jpg',
'secondary_images/123_30.jpg',
'secondary_images/123_20.jpg'
].map{ |s|
get_image_num(s)
}
# => [["123", "1"], ["123", "11"], ["123", "01"], ["123", "30"], ["123", "20"]]

creating an array using find method in rails

input = {"color"=>["red"],"size"=>["s","l"]}
json_obj = [{"color":"red","id":"123","size":"s","name":"test"},
{"color":"yellow","id":"124","size":"s","name":"test"},
{"color":"red","id":"125","size":"l","name":"test"}]
Output should be
output["red_s"] = {"color":"red","id":"123","size":"s","name":"test"}
output["red_l"] = {"color":"red","id":"125","size":"l","name":"test"}
output is the combinations of the input and a find on the json_obj.
How to get the output in rails?
I have the below script to get the combinations ie.red_s and red_l,
ary = input.map {|k,v| [k].product v}
output = ary.shift.product(*ary).map {|a| Hash[a]}
And
output[red_s]=json_obj.find{|h| h["color"] == "red" and h["size"] == "S"}
I don't want to have any hardcodings in code like color and size as above.
I think this should get you close to what you want.
Note the "ticks" around your json array object (what you had is not valid ruby)
The other issue is you would have to figure a better way to create the output hash key.
require 'json'
input = {"color"=>["red"],"size"=>["s","l"]}
output = {}
json_obj = '[{"color":"red","id":"123","size":"s","name":"test"},
{"color":"yellow","id":"124","size":"s","name":"test"},
{"color":"red","id":"125","size":"l","name":"test"}]'
found = JSON.parse json_obj
input.each_key do |key|
found = found.select { |item| input[key].include?(item[key]) }
end
puts found
found.each do |item|
output_key = ""
input.each_key do |key|
output_key = "#{item[key]}_" + output_key
end
output["#{output_key}"] = item.to_json
end
puts output

How to refactor each function with map in Ruby?

I have a loop building a hash for use in a select field. The intention is to end up with a hash:
{ object.id => "object name", object.id => "object name" }
Using:
#hash = {}
loop_over.each do |ac|
#hash[ac.name] = ac.id
end
I think that the map method is meant for this type of situation but just need some help understanding it and how it works. Is map the right method to refactor this each loop?
Data transformations like this are better suited to each_with_object:
#hash = loop_over.each_with_object({}) { |ac, h| h[ac.name] = ac.id }
If your brain is telling you to use map but you don't want an array as the result, then you usually want to use each_with_object. If you want to feed the block's return value back into itself, then you want inject but in cases like this, inject requires a funny looking and artificial ;h in the block:
#hash = loop_over.inject({}) { |h, ac| h[ac.name] = ac.id; h }
# -------------------- yuck -----------------------------^^^
The presence of the artificial return value is the signal that you want to use each_with_object instead.
Try:
Hash[loop_over.map { |ac| [ac[:name], ac[:id]] }]
Or if you are running on Ruby 2:
loop_over.map { |ac| [ac[:name], ac[:id]] }.to_h
#hash = Hash[loop_over.map { |ac| {ac.name => ac.id} }.map(&:flatten)]
Edit, a simpler solution as per suggestion in a comment.
#hash = Hash[ loop_over.map { |ac| [ac.name, ac.id] } ]
You can simply do this by injecting a blank new Hash and performing your operation:
loop_over.inject({}){ |h, ac| h[ac.name] = ac.id; h }
Ruby FTW
No a map isn't the correct tool for this.
The general use-case of a map is to take in an array, perform an operation on each element, and spit out a (possibly) new array (not a hashmap) of the same length, with the individual element modifications.
Here's an example of a map
x = [1, 2, 3, 4].map do |i|
i+1 #transform each element by adding 1
end
p x # will print out [2, 3, 4, 5]
Your code:
#hash = {}
loop_over.each do |ac|
#hash[ac.name] = ac.id
end
There is nothing wrong with this example. You are iterating over a list, and populating a hashmap exactly as you wished.
Ruby 2.1.0 introduces brand new method to generate hashes:
h = { a: 1, b: 2, c: 3 }
h.map { |k, v| [k, v+1] }.to_h # => {:a=>2, :b=>3, :c=>4}
I would go for the inject version, but use update in the block to avoid the easy to miss (and therefore error prone) ;h suffix:
#hash = loop_over.inject({}) { |h, ac| h.update(ac.name: ac.id) }

Easier way to write If hash includes then - Ruby

I have the following in an initialize method on my model:
#home_phone = contact_hash.fetch('HomePhone')
However, sometimes I need this instead:
#home_phone = contact_hash.fetch('number')
Also, sometimes neither of those will be true and I will need the home_phone attribute to be empty.
How can I write this out without creating a big loop like so:
if contact_hash.has_key?('HomePhone')
#home_phone = contact_hash.fetch('HomePhone')
elsif contact_hash.has_key?('number')
#home_phone = contact_hash.fetch('number')
else
#home_phone = ""
end
You could try
#home_phone = contact_hash.fetch('HomePhone', contact_hash.fetch('number', ""))
or better
#home_phone = contact_hash['HomePhone'] || contact_hash['number'] || ""
contact_hash.values_at('HomePhone','number','home_phone').compact.first
Edit:
My first solution did not really give the answer asked for. Here is a modified version, although I think in the case of only 3 options the solution given by #knut is better.
contact_hash.values_at('HomePhone','number').push('').compact.first
def doit(h, *args)
args.each {|a| return h[a] if h[a]}
""
end
contact_hash = {'Almost HomePhone'=>1, 'number'=>7}
doit(contact_hash, 'HomePhone', 'number') # => 7
You could use values_at I suppose:
#home_phone = contact_hash.values_at('HomePhone', 'number').find(&:present?).to_s
That isn't exactly shorter but it wouldn't be convenient if you had the keys in an array:
try_these = %w[HomePhone number]
#home_phone = contact_hash.values_at(*try_these).find(&:present?).to_s
You could also wrap that up in a utility method somewhere or patch it into Hash.

Ruby on Rails: Array to Hash with (key, array of values)

Lets say I have an Array of content_categories (content_categories = user.content_categories)
I now want to add every element belonging to a certain categorie to content_categories with the category as a key and the the content-item IDs as elements of a set
In PHP something like this is possible:
foreach ($content_categories as $key => $category) {
$contentsByCategoryIDArray = Category.getContents($category[id])
$content_categories[$key][$contentsByCategoryIDArray]
}
Is there an easy way in rails to do this?
Greets,
Nico
Your question isn't really a Rails question, it's a general Ruby programming question.
Your description isn't very clear, but from what I understand, you want to group IDs for common categories using a Hash. There are various other ways of doing this, but this is easy to understand::
ary = [
'cat1', {:id => 1},
'cat2', {:id => 2},
'cat1', {:id => 3}
]
hsh = {}
ary.each_slice(2) { |a|
key,category = a
hsh[key] ? hsh[key] << category[:id] : hsh[key] = [category[:id]]
}
hsh # => {"cat1"=>[1, 3], "cat2"=>[2]}
I'm using a simple Array with a category, followed by a simple hash representing some object instance, because it makes it easy to visualize. If you have a more complex object, replace the hash entries with those objects, and tweak how you access the ID in the ternary (?:) line.
Using Enumerable.inject():
hsh = ary.each_slice(2).inject({}) { |h,a|
key,category = a
h[key] ? h[key] << category[:id] : h[key] = [category[:id]]
h
}
hsh # => {"cat1"=>[1, 3], "cat2"=>[2]}
Enumerable.group_by() could probably shrink it even more, but my brain is fading.
I'd use Enumerable#inject
content_categories = content_categories_array.inject({}){ |memo, category| memo[category] = Category.get_contents(category); memo }
Hash[content_categories.map{|cat|
[cat, Category.get_contents(cat)]
}]
Not really the right answer, because you want IDs in your array, but I post it anyway, because it's nice and short, and you might actually get away with it:
content_categories.group_by(&:category)
content_categories.each do |k,v|
content_categories[k] = Category.getContents(v)
end
I suppose it's works
If i understand correctly, content_categories is an array of categories, which needs to be turned into a hash of categories, and their elements.
content_categories_array = content_categories
content_categories_hash = {}
content_categories_array.each do |category|
content_categories_hash[category] = Category.get_contents(category)
end
content_categories = content_categories_hash
That is the long version, which you can also write like
content_categories = {}.tap do |hash|
content_categories.each { |category| hash[category] = Category.get_contents(category) }
end
For this solution, content_categories must be a hash, not an array as you describe. Otherwise not sure where you're getting the key.
contents_by_categories = Hash[*content_categories.map{|k, v| [k, Category.getContents(v.id)]}]

Resources