How can I shrink this code to a one-liner? - ruby-on-rails

I am using CarrierWave and find myself doing this all the time:
fin = File.open(payload_file_name,'rb')
self.payload = fin
fin.close
where payload is my CarrierWave field. I considered doing:
self.payload = File.open(payload_file_name,'rb')
but I was concerned it would leave the file open and my app server would eventually run out of file pointers. Is there a Rubyism that will let me assign the return value of a simple block to a variable?

This is possible using a block with open:
File.open(payload_file_name,'rb') do |fin|
self.payload = fin.read
end
You can reduce that further, to a single line, but it won't accomplish anything additional:
File.open(payload_file_name,'rb') { |fin| self.payload = fin.read }
You could also use read:
self.payload = File.read(payload_file_name, :mode => 'rb')
But you knew all that after reading the documentation, right?

You can pass a block to File.open() and it will close automatically after the block finishes execution. Something like(untested):
File.open('payload_file_name', 'rb') do | file |
# do stuff with file
end
Or the one-liner version.
File.open('payload_file_name', 'rb') {|file| # do stuff }

Using IO::read:
self.payload = File.read(payload_file_name, mode: 'rb')

Related

Show square brackets in yml file

How to generate content in yml file with this format:
required_groups:
- ["member", "cn=serviceboard-users,cn=groups,cn=accounts,dc=int,dc=dostack,dc=io"]
Instead of this format:
required_groups:
- - member
- cn=serviceboard-users,cn=groups,cn=accounts,dc=int,dc=dostack,dc=io
Input:
[["member", "cn=serviceboard-users,cn=groups,cn=accounts,dc=int,dc=dostack,dc=io"]]
Current Code:
File.open("config/ldap/#{Rails.env}.yml", "w") do |file|
data = {}
formatted_groups_array = []
Setting.ldap_users_filter_required_groups.each { |group| formatted_groups_array.push([Setting.ldap_group_membership_attribute.to_s, group.to_s])}
data["required_groups"] = formatted_groups_array
file.write(data.to_yaml)
end
As stated in this answer, the default ruby library Psych does not have many options available, and you cannot customize the output the way you are trying to do.
In the meantime I've found an old possibile custom solution to your problem, but I had to rewrite a small part of it to make it compatible to the most recent ruby version (I am using 2.7.0 for this example). This is my updated script. Basically it will introduce a new way to edit the YAML output using a more concise syntax.
You can use it this way:
File.open("config/ldap/#{Rails.env}.yml", "w") do |file|
data = {}
formatted_groups_array = []
Setting.ldap_users_filter_required_groups.each { |group| formatted_groups_array.push([Setting.ldap_group_membership_attribute.to_s, group.to_s])}
data["required_groups"] = formatted_groups_array
styled_yaml = StyledYAML.inline(data)
file.write(StyledYAML.dump(styled_yaml))
end

Is there a possiblity to create a variable with a string for everything inside of a table?

Just wondering about that, because i don't find a solution for it. Pretty sure because I'm new to this c: Thanks for your help.
Edit: for explanation im gonna explain it with the code a bit.
local FileList = fs.list("") --Makes a Table with all the files and directories available (as strings) on the PC it's running on (Inside of a mod called computercraft for minecraft)
for _, file in ipairs(FileList) do
--Here I need a function which assigns every string from the table to a variable, just for the explanation I'll call it unknown.function
unknown.function
end
while true do
print(a) --a is for example one of the different variables made from the "unknown.function"
sleep(1)
end
LIKE this?
AllFiles = {}
function Crazyfunction(file)
AllFiles[table.getn(AllFiles)+1] = file
end
local FileList = fs.list("")
for _, file in ipairs(FileList) do
Crazyfunction(file)
end
while true do
print(AllFiles[NUMBE_OF_FILE_HERE])
sleep(1)
end
You mean like this?
local FileList = fs.list("")
for _, file in ipairs(FileList) do
-- file is the variable which contains a string from the table.
print(file)
sleep(1)
end
What you want is... already what your loop does. You just added an extra loop for no reason.

Prevent a file being written to more than once without file:close()

I'm currently working on a logging system. My problems arise when using for loops and file writing. Here is a small example:
file = io.open("text.txt","a") --text.txt can be literally anything
for i=1,8 do
if x == true then
file:write("X is true.")
elseif y == true then
file:write("Y is true.")
end
end
Is there a way to stop the file from being written to multiple times without using file:close()? I have a huge number of different file:write sections, and adding file:close() after all of them would be a massive problem.
If file:close() every time is a massive problem, then this is the custom logic you need.
myFileMetatable = {} --implement all necessary file operations here
function myFileMetatable.write(self, str)
if not self.written then
self.written = true
self.f:write(str)
end
end
function myFileMetatable.close(self)
self.f:close()
end
myFile = {}
function myFile.open(filename, mode)
local t = {f = io.open(filename, mode)}
setmetatable(t, {__index = myFileMetatable})
return t
end
--now you can do
file = myFile.open("test", "w")
file:write("test")
file:write("hello")
file:write("world")
file:close() --and only "test" will be written
Note that this is probably much better than replacing file:write(str) with something file_write(file, str), since you need to store somewhere the fact that the file has already been written to, which you cannot store inside the FILE* object and using a global variable for that will break when using multiple files. That's why I wrap the FILE* object in a table and use myFileMetatable to implement my own methods that I will need.
However, if you need just one file at a time and don't mind the global variable then this is more efficient.
file_written = false
function file_write(file, str)
if not file_written then
file_written = true
file:write(str)
end
end
file = io.open("test", "w")
file_write(file, "test")
file_write(file, "hello")
file_write(file, "world")
file:close()
Mind that it's not as pretty as the first example and you might face a problem in the future, if you decide to expand beyond one file.
How Egor Skriptunoff already said, I'll recommend you to write your own writing function. I'm normally using sth. like this:
local function writeFile(filePath, str)
local outfile = io.open(filePath, 'w')
outfile:write(str)
outfile:close()
end
For appending to the file easily change the mode from w to a.
For my specific case, I've found a solution - since I just want a single option to print, and it's the last option (rather than the first, and I should've specified this), I can just set a variable to what I want my output to be and write that at the end.
log = ""
if x == 2 then
log = "X is 2."
elseif y == 2 then
log = "Y is 2."
end
file:write(log)
For the last option, I'd refer anyone to the accepted answer which should be perfect.

How to check if a file is a text file?

Does Perl6 have something like the Perl5 -T file test to tell if a file is a text file?
There's nothing built in, however there is a module Data::TextOrBinary that does that.
use Data::TextOrBinary;
say is-text('/bin/bash'.IO); # False
say is-text('/usr/share/dict/words'.IO); # True
That's a heuristic that has not been translated to Perl 6. You can simply read it in UTF8 (or ASCII) to do the same:
given slurp("read-utf8.p6", enc => 'utf8') -> $f {
say "UTF8";
}
(substitute read-utf8.p6 by the name of the file you want to check)
we can make use of the File::Type with the following code.
use strict;
use warnings;
use File::Type;
my $file = '/path/to/file.ext';
my $ft = File::Type->new();
my $file_type = $ft->mime_type($file);
if ( $file_type eq 'application/octet-stream' ) {
# possibly a text file
}
elsif ( $file_type eq 'application/zip' ) {
# file is a zip archive
}
Source: https://metacpan.org/pod/File::Type

Saving images with Carrierwave inside a ruby worker

I'm trying to write a method to store an image from a given url, inside a ruby worker. It comes along my Rails app in which I display the object image.
Here is what I've come up with :
def store(url)
#object = Object.find(1)
#object[:image] = CarrierWave::Uploader.store!(image_url)
end
It doesn't seem to work at all.
Any clues?
Is there another way around?
[EDIT]
Here is the current situation :
def store
#object = Object.find(1)
my_uploader = ImageUploader.new
image = open("http://twitpic.com/show/iphone/xxxx.jpg")
# or for a local file:
image = File.open(Rails.root.join('xxxx.png'))
#object[:image] = my_uploader.store!(image)
#object.save!
end
The filename in the [:image] attibute is still wrong. It gives "[:store_versions!]". How do I get the filename right?
[EDIT2]
Got the filename right by adding #artwork[:image] = my_uploader.filename before save.
But #object = Object.find(1) won't work. How do I access the Object class, which is inside my rails app, from the worker?
#object.image.store!(image) finally did the job!
You'll want to create a new uploader object and point it to your file
image = File.open(Rails.root.join('path', 'to', 'file.png'))
#object[:image] = YourUploader.new(image)

Resources