I have a model Report to export all the Pdf attachments done for the bills. I have other models Bill, Upload.
Bill has many uploads
Tech Stack used
Ruby 2.5.8
Rails 5.0.7
Dummy Code
require 'fileutils'
require 'zip'
require 'open-uri'
class Report < ApplicationRecord
main_dir = "tmp/Attachment-#{self.id}"
FileUtils::mkdir_p main_dir
bills.each do |bill|
bill_directory_name = 'Bill_#{bill.id}'
bill.uploads.each do |bill_upload|
begin
FileUtils::cd main_dir do
FileUtils::mkdir_p "#{bill_directory_name}"
file = open(bill_upload.file.url, 'rb')
upload_name = "Invoice-#{bill_upload.file_file_name}-#{bill_upload.id}"
FileUtils::cd "#{bill_directory_name}" do
IO.copy_stream(file, "#{upload_name}")
end
end
rescue => exp
Airbrake.notify(exp)
end
end
end
path = "tmp/Attachment-#{self.id}"
archive = File.join(path, "Attachment-#{self.id}" + '.zip')
FileUtils.rm archive, force: true
Zip::File::open(archive, 'w') do |zipfile|
Dir["#{path}/**/**"].reject{|f| f==archive }.each do |file|
zipfile.add(file.sub(path + '/', ''), file)
end
end
end
Zip folder is created successfully. When I extract zip folder, I can see there are many sub folders created for bills. Each bill folder have uploaded PDF files as well and it's working.
Problem
Most of the time exported bill sub-directory contains PDF uploaded file. But, rarely sometime (once in a 100's of iteration), when I export report, I cannot see PDF file inside some of the bill sub directory. I have added error handling but there is no error generated. So, it's hard to reproduce this issue at my end.
How to confirm that this file copy command is successful IO.copy_stream()?
Please check dummy code and provide your feedback if I need to improve or modify any line of code?
file = open(bill_upload.file.url, 'rb') # File is read from S3
upload_name = "Invoice-#{bill_upload.file_file_name}-#{bill_upload.id}"
FileUtils::cd "#{bill_directory_name}" do
IO.copy_stream(file, "#{upload_name}")
end
Any help would be appreciated.
I don't know that much about RUBY, just thought that you guys might help me with this. I'm using Storyblok as my headless CMS and JEKYLL when I'm building serve it this is the error that I got;
33: from C:/project/test/_plugins/storyblok_generator.rb:8:in `generate'
32: from C:/project/test/_plugins/storyblok_cms/generator.rb:12:in `generate!'
C:/project/test/vendor/cache/ruby/2.7.0/gems/storyblok-3.0.1/lib/storyblok/client.rb:354:in `block (2 levels) in find_and_fill_links': undefined method `[]' for nil:NilClass (NoMethodError)
the code below is from _plugins/storyblok_cms/generator.rb
def generate!
timestamp = Time.now.to_i
links = client.links(cv: timestamp)['data']['links']
stories = client.stories(per_page: 100, page: 1, cv: timestamp)['data']['stories'] #line 12
stories.each do |story|
# create all pages except global (header,footer,etc.)
content_type = story['content']['component']
if content_type != 'shared'
site.pages << create_page(site, story, links)
end
rescue UnknownContentTypeError => e
# for production, raise unknown content type error;
# for preview and other environments, issue an warning only since the content_type might be available
# but the code handling that content type might be in a different branch.
Jekyll.env == 'production' ? raise : Jekyll.logger.warn(e.message)
end
site.data['stories'] = stories
site.data['articles'] = stories.select { |story| story['full_slug'].start_with?('articles') }
site.data['shared'] = stories.select { |story| story['full_slug'].start_with?('shared') }
end
the code below is from _plugins/storyblok_generator.rb
require "storyblok"
module Jekyll
class StoryblokGenerator < Jekyll::Generator
safe true
def generate(site)
StoryblokCms::Generator.new(site).generate! #line 8
end
end
end
Additional Info:
ruby version: ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x64-mingw32]
jekyll version: jekyll 4.2.1
OS: Windows 10
I've actually found the solution to this, So this error occurs because I have created a page template in Block Library in Storyblok and then firing bundle exec jekyll serve command in terminal without creating the page template source/file in my project directory.
So I have an about_us block (content-type) in Block Library created and then when I fire bundle exec jekyll serve without creating an about_us.html first in _layouts folder, It would trigger the error.
Solution;
Make sure to create the source/file first in the _layouts folder if you have created a block (content-type) in block library.
For speed up development process, I'm using file zze.rb, which I edit in my IDE and reload in pry console with short command zze (zz - uncommon beginning for variable/method names; e - execute). This way I can stop execution process in any point of my application by binding.pry and can execute code many times, not waiting of full Rails environment restart each time.
I have this code in my .pryrc file:
Pry.config.commands.command "zze", 'Execute all from .pry_exec/zze.rb' do
Dir['.pry_exec/autoload/**/*.rb']
.delete_if {|file| File.basename(file) =~ /^_/} # inore files that starting form underscore '_'
.each { |f| load(f) }
file_name = File.absolute_path '.pry_exec/zze.rb'
code = File.open(file_name, 'r') {|f| f.read}
eval(code, #target, file_name)
end
and have folder <myproject>/.pry_exec/:
<myproject>/.pry_exec/ # ignored from CVS
autoload/ # this folder loads automatically each `zze` call in cosnole
some_class1.rb
some_code2.rb
zze.rb # this file loads automatically each `zze` call in console.
Sometimes there are cases, when it would be good to use binding.pry inside <myproject>/zze.rb or <myproject>/autoload/some_class1.rb but it does not working. It just ignore that.
I also tried to rewrite zze-code this way:
# ~/.pryrc
def zze(name = nil)
Dir['.pry_exec/autoload/**/*.rb']
.delete_if {|file| File.basename(file) =~ /^_/} # inore files that starting form underscore '_'
.each { |f| load(f) }
load ".pry_exec/#{name || 'zze'}.rb"
# instance_eval(File.read(".pry_exec/#{name || 'zze'}.rb"))
end
but it also ignore binding.pry inside <myproject>/zze.rb or <myproject>/autoload/some_class1.rb
Also when zze is not pry-command, but is method, code from zze.rb executes inside another scope, and I will not have access to local variables defined from zze.rb in my pry-main context. Didn't find how to fix that yet with short way.
I am trying to create a report in simplecov to report on rogue actions within my application. Basically I want a tab that reports any and only files that use the :nocov: parameter to prevent simplecov from reporting it. Now, I know these may not be all bad, so I only want to filter them to a tab and not affect the overall score.
Currently I have the custom tab setup, but the filter does not filter the files correctly. Can anyone point me in the right direction?
Sample simple cov ignored method:
# :nocov:
def my_debug_method
do_something
end
# :nocov:
Here is my current .simplecov setup:
class IgnoredCodeFilter < SimpleCov::Filter
def matches?(src_file)
src_file.grep(/:nocov:/).size > 0
end
end
SimpleCov.start do
add_group "Ignored Code" do |src_file|
IgnoredCodeFilter.new(src_file)
end
end
Current error message:
Formatter SimpleCov::Formatter::HTMLFormatter failed with NoMethodError: undefined method `grep' for #<SimpleCov::SourceFile:0x007f920e166fa0> (.simplecov:13:in `block (2 levels) in <top (required)>')
Here is how I ultimately solved this issue in case others are looking for it.
Just add this to your .simplecov configuration file:
SimpleCov.start do
add_group "Ignored Code" do |src_file|
open(src_file.filename).grep(/:nocov:/).any?
end
end
What is the best way to get a temporary directory with nothing in it using Ruby on Rails? I need the API to be cross-platform compatible. The stdlib tmpdir won't work.
The Dir object has a method mktmpdir which creates a temporary directory:
require 'tmpdir' # Not needed if you are using rails.
Dir.mktmpdir do |dir|
puts "My new temp dir: #{dir}"
end
The temporary directory will be removed after execution of the block.
The Dir#tmpdir function in the Ruby core (not stdlib that you linked to) should be cross-platform.
To use this function you need to require 'tmpdir'.
A general aprox I'm using now:
def in_tmpdir
path = File.expand_path "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/"
FileUtils.mkdir_p path
yield path
ensure
FileUtils.rm_rf( path ) if File.exists?( path )
end
So in your code you can:
in_tmpdir do |tmpdir|
puts "My tmp dir: #{tmpdir}"
# work with files in the dir
end
The temporary dir will be removed automatically when your method will finish.
Ruby has Dir#mktmpdir, so just use that.
require 'tempfile'
Dir.mktmpdir('prefix_unique_to_your_program') do |dir|
### your work here ###
end
See http://www.ruby-doc.org/stdlib-1.9.3/libdoc/tmpdir/rdoc/Dir.html
Or build your own using Tempfile tempfile that is process and thread unique, so just use that to build a quick Tempdir.
require 'tempfile'
Tempfile.open('prefix_unique_to_your_program') do |tmp|
tmp_dir = tmp.path + "_dir"
begin
FileUtils.mkdir_p(tmp_dir)
### your work here ###
ensure
FileUtils.rm_rf(tmp_dir)
end
end
See http://www.ruby-doc.org/stdlib-1.9.3/libdoc/tempfile/rdoc/Tempfile.html for optional suffix/prefix options.
require 'tmpdir' # not needed if you are loading Rails
tmp_dir = File.join(Dir::tmpdir, "my_app_#{Time.now.to_i}_#{rand(100)}")
Dir.mkdir(tmp_dir)
Works for me.
You can use Dir.mktmpdir.
Using a block will get rid of the temporary directory when it closes.
Dir.mktmpdir do |dir|
File.open("#{dir}/foo", 'w') { |f| f.write('foo') }
end
Or if you need multiple temp directories to exist at the same time, for example
context 'when there are duplicate tasks' do
it 'raises an DuplicateTask error' do
begin
tmp_dir1 = Dir.mktmpdir('foo')
tmp_dir2 = Dir.mktmpdir('bar')
File.new("#{tmp_dir1}/task_name", 'w+')
File.new("#{tmp_dir2}/task_name", 'w+')
expect { subject.filepath('task_name') }.to raise_error(TaskFinder::DuplicateTask)
ensure
FileUtils.remove_entry tmp_dir1
FileUtils.remove_entry tmp_dir2
end
end
end
Dir.mktmpdir creates a temporary directory under Dir.tmpdir (you'll need to require 'tmpdir' to see what that evaluates to).
If you want to use your own path, Dir.mktmpdir takes an optional second argument tmpdir if non-nil value is given. E.g.
Dir.mktmpdir(nil, "/var/tmp") { |dir| "dir is '/var/tmp/d...'" }
I started to tackle this by hijacking Tempfile, see below.
It should clean itself up as Tempfile does, but doesn't always yet..
It's yet to delete files in the tempdir.
Anyway I share this here, might be useful as a starting point.
require 'tempfile'
class Tempdir < Tempfile
require 'tmpdir'
def initialize(basename, tmpdir = Dir::tmpdir)
super
p = self.path
File.delete(p)
Dir.mkdir(p)
end
def unlink # copied from tempfile.rb
# keep this order for thread safeness
begin
Dir.unlink(#tmpname) if File.exist?(#tmpname)
##cleanlist.delete(#tmpname)
#data = #tmpname = nil
ObjectSpace.undefine_finalizer(self)
rescue Errno::EACCES
# may not be able to unlink on Windows; just ignore
end
end
end
This can be used the same way as Tempfile, eg:
Tempdir.new('foo')
All methods on Tempfile , and in turn, File should work.
Just briefly tested it, so no guarantees.
Update: gem install files, then
require "files"
dir = Files do
file "hello.txt", "stuff"
end
See below for more examples.
Here's another solution, inspired by a few other answers. This one is suitable for inclusion in a test (e.g. rspec or spec_helper.rb). It makes a temporary dir based on the name of the including file, stores it in an instance variable so it persists for the duration of the test (but is not shared between tests), and deletes it on exit (or optionally doesn't, if you want to check its contents after the test run).
def temp_dir options = {:remove => true}
#temp_dir ||= begin
require 'tmpdir'
require 'fileutils'
called_from = File.basename caller.first.split(':').first, ".rb"
path = File.join(Dir::tmpdir, "#{called_from}_#{Time.now.to_i}_#{rand(1000)}")
Dir.mkdir(path)
at_exit {FileUtils.rm_rf(path) if File.exists?(path)} if options[:remove]
File.new path
end
end
(You could also use Dir.mktmpdir (which has been around since Ruby 1.8.7) instead of Dir.mkdir but I find the API of that method confusing, not to mention the naming algorithm.)
Usage example (and another useful test method):
def write name, contents = "contents of #{name}"
path = "#{temp_dir}/#{name}"
File.open(path, "w") do |f|
f.write contents
end
File.new path
end
describe "#write" do
before do
#hello = write "hello.txt"
#goodbye = write "goodbye.txt", "farewell"
end
it "uses temp_dir" do
File.dirname(#hello).should == temp_dir
File.dirname(#goodbye).should == temp_dir
end
it "writes a default value" do
File.read(#hello).should == "contents of hello.txt"
end
it "writes a given value" do
# since write returns a File instance, we can call read on it
#goodbye.read.should == "farewell"
end
end
Update: I've used this code to kickstart a gem I'm calling files which intends to make it super-easy to create directories and files for temporary (e.g. unit test) use. See https://github.com/alexch/files and https://rubygems.org/gems/files . For example:
require "files"
files = Files do # creates a temporary directory inside Dir.tmpdir
file "hello.txt" # creates file "hello.txt" containing "contents of hello.txt"
dir "web" do # creates directory "web"
file "snippet.html", # creates file "web/snippet.html"...
"<h1>Fix this!</h1>" # ...containing "<h1>Fix this!</h1>"
dir "img" do # creates directory "web/img"
file File.new("data/hello.png") # containing a copy of hello.png
file "hi.png", File.new("data/hello.png") # and a copy of hello.png named hi.png
end
end
end # returns a string with the path to the directory
Check out the Ruby STemp library: http://ruby-stemp.rubyforge.org/rdoc/
If you do something like this:
dirname = STemp.mkdtemp("#{Dir.tmpdir}/directory-name-template-XXXXXXXX")
dirname will be a string that points to a directory that's guaranteed not to exist previously. You get to define what you want the directory name to start with. The X's get replaced with random characters.
EDIT: someone mentioned this didn't work for them on 1.9, so YMMV.