Ruby - Find the ending of a filename within certain parameters - ruby-on-rails

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"]]

Related

ruby add strings from array

I am building a simple breadcrumb in ruby but I am not sure how to really implement my logic.
Let's say I have an array of words that are taken from my request.path.split("/) ["", "products", "women", "dresses"]
I want to push the strings into another array so then in the end I have ["/", "/products", "products/women", "products/women/dresses"] and I will use it as my breadcrumb solution.
I am not good at ruby but for now I came up with following
cur_path = request.path.split('/')
cur_path.each do |link|
arr = []
final_link = '/'+ link
if cur_path.find_index(link) > 1
# add all the previous array items with the exception of the index 0
else
arr.push(final_link)
end
end
The results should be ["/", "/products", "/products/women", "/products/women/dresses"]
Ruby's Pathname has some string-based path manipulation utilities, e.g. ascend:
require 'pathname'
Pathname.new('/products/women/dresses').ascend.map(&:to_s).reverse
#=> ["/", "/products", "/products/women", "/products/women/dresses"]
This is my simplest solution:
a = '/products/women/dresses'.split('/')
a.each_with_index.map { |e,i| e.empty? ? '/' : a[0..i].join('/') }
Using map and with_index it can be done like this:
arr = ["", "products", "women", "dresses"]
arr.map.with_index { |item, index| "#{arr[0...index].join('/')}/#{item}" }
This is another option using Enumerable#each_with_object and Enumerable#each_with_index:
ary = '/products/women/dresses'.split('/')
ary[1..].map
.with_index
.with_object([]) { |(folder, idx), path| path << [path[idx-1], folder].join('/') }.unshift('/')
Or also:
(ary.size - 1).times.map { |i| ary.first(i + 2).join('/') }.unshift('/')

One ' to much in string

I try to write to an string something like this:
arr << "Icd3code.create!({:text => '#{variable1}'})" + "\n"
My problem is that variable 1 is an string, that contains an ' :
variable1 = "Ami's house"
So that at the end the ouput of my code is this:
Icd3code.create!({:text => 'Ami's house'})
How you can see now i have one ' to much! I dont know what i can do to avoid this problem! Thanks
If I've understood, you want to loop over some input, building up a list of parameters, which you plan to later use to create some records. If that's the case, I think you're better off using hashes, instead of strings:
# Let's pretend this came from the big, bad, world
inputs = ["Ami's house", "Fred's house", "Jim's house"]
creation_params = []
inputs.each do |input|
creation_params << {:text => input}
end
Then you could create all the Icd3codes, like this:
creation_params.each do |params|
Icd3code.create!(params)
end
Or you could save them in a text file, for later:
File.open('dest', 'w') do |f|
f.write(creation_params.to_json)
end
variable1 = "Ami's house"
puts %Q[Icd3code.create!({:text => "#{variable1}"})] + "\n"
--output:--
Icd3code.create!({:text => "Ami's house"})

Rspec Ruby on Rails Test File System in model

I have a model that has a method that looks through the filesystem starting at a particular location for files that match a particular regex. This is executed in an after_save callback. I'm not sure how to test this using Rspec and FactoryGirl. I'm not sure how to use something like FakeFS with this because the method is in the model, not the test or the controller. I specify the location to start in my FactoryGirl factory, so I could change that to a fake directory created by the test in a set up clause? I could mock the directory? I think there are probably several different ways I could do this, but which makes the most sense?
Thanks!
def ensure_files_up_to_date
files = find_assembly_files
add_files = check_add_assembly_files(files)
errors = add_assembly_files(add_files)
if errors.size > 0 then
return errors
end
update_files = check_update_assembly_files(files)
errors = update_assembly_files(update_files)
if errors.size > 0 then
return errors
else
return []
end
end
def find_assembly_files
start_dir = self.location
files = Hash.new
if ! File.directory? start_dir then
errors.add(:location, "Directory #{start_dir} does not exist on the system.")
abort("Directory #{start_dir} does not exist on the system for #{self.inspect}")
end
Find.find(start_dir) do |path|
filename = File.basename(path).split("/").last
FILE_TYPES.each { |filepart, filehash|
type = filehash["type"]
vendor = filehash["vendor"]
if filename.match(filepart) then
files[type] = Hash.new
files[type]["path"] = path
files[type]["vendor"] = vendor
end
}
end
return files
end
def check_add_assembly_files(files=self.find_assembly_files)
add = Hash.new
files.each do |file_type, file_hash|
# returns an array
file_path = file_hash["path"]
file_vendor = file_hash["vendor"]
filename = File.basename(file_path)
af = AssemblyFile.where(:name => filename)
if af.size == 0 then
add[file_path] = Hash.new
add[file_path]["type"] = file_type
add[file_path]["vendor"] = file_vendor
end
end
if add.size == 0 then
logger.error("check_add_assembly_files did not find any files to add")
return []
end
return add
end
def check_update_assembly_files(files=self.find_assembly_files)
update = Hash.new
files.each do |file_type, file_hash|
file_path = file_hash["path"]
file_vendor = file_hash["vendor"]
# returns an array
filename = File.basename(file_path)
af = AssemblyFile.find_by_name(filename)
if !af.nil? then
if af.location != file_path or af.file_type != file_type then
update[af.id] = Hash.new
update[af.id]['path'] = file_path
update[af.id]['type'] = file_type
update[af.id]['vendor'] = file_vendor
end
end
end
return update
end
def add_assembly_files(files=self.check_add_assembly_files)
if files.size == 0 then
logger.error("add_assembly_files didn't get any results from check_add_assembly_files")
return []
end
asm_file_errors = Array.new
files.each do |file_path, file_hash|
file_type = file_hash["type"]
file_vendor = file_hash["vendor"]
logger.debug "file type is #{file_type} and path is #{file_path}"
logger.debug FileType.find_by_type_name(file_type)
file_type_id = FileType.find_by_type_name(file_type).id
header = file_header(file_path, file_vendor)
if file_vendor == "TBA" then
check = check_tba_header(header, file_type, file_path)
software = header[TBA_SOFTWARE_PROGRAM]
software_version = header[TBA_SOFTWARE_VERSION]
elsif file_vendor == "TBB" then
check = check_tbb_header(header, file_type, file_path)
if file_type == "TBB-ANNOTATION" then
software = header[TBB_SOURCE]
else
software = "Unified"
end
software_version = "UNKNOWN"
end
if check == 0 then
logger.error("skipping file #{file_path} because it contains incorrect values for this filetype")
asm_file_errors.push("#{file_path} cannot be added to assembly because it contains incorrect values for this filetype")
next
end
if file_vendor == "TBA" then
xml = header.to_xml(:root => "assembly-file")
elsif file_vendor == "TBB" then
xml = header.to_xml
else
xml = ''
end
filename = File.basename(file_path)
if filename.match(/~$/) then
logger.error("Skipping a file with a tilda when adding assembly files. filename #{filename}")
next
end
assembly_file = AssemblyFile.new(
:assembly_id => self.id,
:file_type_id => file_type_id,
:name => filename,
:location => file_path,
:file_date => creation_time(file_path),
:software => software,
:software_version => software_version,
:current => 1,
:metadata => xml
)
assembly_file.save! # exclamation point forces it to raise an error if the save fails
end # end files.each
return asm_file_errors
end
Quick answer: you can stub out model methods like any others. Either stub a specific instance of a model, and then stub find or whatever to return that, or stub out any_instance to if you don't want to worry about which model is involved. Something like:
it "does something" do
foo = Foo.create! some_attributes
foo.should_receive(:some_method).and_return(whatever)
Foo.stub(:find).and_return(foo)
end
The real answer is that your code is too complicated to test effectively. Your models should not even know that a filesystem exists. That behavior should be encapsulated in other classes, which you can test independently. Your model's after_save can then just call a single method on that class, and testing whether or not that single method gets called will be a lot easier.
Your methods are also very difficult to test, because they are trying to do too much. All that conditional logic and external dependencies means you'll have to do a whole lot of mocking to get to the various bits you might want to test.
This is a big topic and a good answer is well beyond the scope of this answer. Start with the Wikipedia article on SOLID and read from there for some of the reasoning behind separating concerns into individual classes and using tiny, composed methods. To give you a ballpark idea, a method with more than one branch or more than 10 lines of code is too big; a class that is more than about 100 lines of code is too big.

Params contain an array that wants to be a hash

I have an array (coming from a file_field, :multiple => true) in my params that I want to turn into a hash so I can build associated models for each element and process in my create action.
Currently receiving:
{"gallery"=>{"name"=>"A Gallery", "photos_attributes"=>{"0"=>{"image"=>[#<1st Image data removed for brevity>, #<2nd Image data removed for brevity>]}}}, "commit"=>"Save"}
I'd like to turn it into something like:
{"gallery"=>{"name"=>"A Gallery", "photos_attributes"=>{"0"=>{"image"=>#<1st Image data removed for brevity>}, "1"=>{"image"=>#<1st Image data removed for brevity>}}}, "commit"=>"Save"}
considered something like this but it's clearly wrong:
i = 0
params[:gallery][:photos_attributes]["0"][:image].reduce({}) do |result, element|
result[i++.to_s] = element
end
What's the "Rail's Way"?
You need to return the result hash at the end of each iteration.
i = 0
params[:gallery][:photos_attributes]["0"][:image].reduce({}) do |result, element|
result[(i += 1).to_s] = element
result
end
I've done something similar when receiving data from an iOS device. But, if I understand what you want and what your model(s) look like, to get nested attributes to work you don't want it to look like:
{ "photos_attributes" => { "0" => <image1>, "1" => <image2>, ... }
You want it to look like:
{ "photos_attributes" => [ <image1>, <image2>, ... ] }
And to do that all you need to do is:
params["gallery"]["photos_attributes"] = params["gallery"]["photos_attributes"]["0"]["image"]
Now, if I've misunderstood what you need, to get what you've asked for what you have might work (I don't use much reduce aka inject) or you could use tap:
i = 0
params["gallery"]["photos_attributes"] = {}.tap do |hash|
params["gallery"]["photos_attributes"]["0"]["image"].each do |image|
hash[i.to_s] = image
i = i + 1
end
end
Not a whole lot better IMO.

What's the optimal way to deal with non-existent variables in Rails?

I'm working on a Rails app that pulls data in from Groupon's API and displays them on our site.
Take the follow data structure, for example:
---
- "id": deal one
"options":
"redemptionLocations":
- "streetAddress1": 123 Any Street"
- "id": deal two
"options": []
If I wanted to loop through each deal, and display the streetAddress1 if it exists, what's the optimal way to do that in Rails?
Just do:
if(defined? streetAddress1) then
print streetAddress1 + " is set"
end
Hope it helps
The best practice should be to use present?:
puts "It is #{object.attribute}" if object.attribute.present?
If you have an array of objects and want to loop only over those that have the attribute set, you can use select:
array.select{|object| object.attribute.present?}.each do |object|
...
end
If you have a deeply nested structure you can create a custom function to check if a key exists and display its value:
def nested_value hash, *args
tmp = hash
args.each do |arg|
return nil if tmp.nil? || !tmp.respond_to?(:[]) || (tmp.is_a?(Array) && !arg.is_a?(Integer))
tmp = tmp[arg]
end
tmp
end
For example, if you have the following YAML loaded from your example:
k = [
{ "id"=>"deal one",
"options"=>{"redemptionLocations"=>[{"streetAddress1"=>"123 Any Street\""}]}},
{ "id"=>"deal two",
"options"=>[]}]
Then you can do this:
nested_value k.first, 'options', 'redemptionLocations', 0, 'streetAddress1'
=> "123 Any Street \""
nested_value k.last, 'options', 'redemptionLocations', 0, 'streetAddress1'
=> nil

Resources