Rails, use the content of file in the controller - ruby-on-rails

I've a file in the config directory, let's say my_policy.txt.
I want to use the content of that file in my controller like a simple string.
#policy = #content of /config/my_policy.txt
How to achieve that goal, does Rails provide its own way to do that?
Thanks

Rails doesn't provide a way, but Ruby does:
#policy = IO.read("#{Rails.root}\config\my_policy.txt")

#policy = File.read(RAILS_ROOT + '/config/my_policy.txt')
To also cache the content (if you don't want to read it every time the variable is used):
def policy
##policy ||= File.read(RAILS_ROOT + '/config/my_policy.txt')
end
If you need something more elegant for configuration, check configatronic.

Read file as string like that?!
def get_file_as_string(filename)
data = ''
f = File.open(filename, "r")
f.each_line do |line|
data += line
end
return data
end
##### MAIN #####
#policy = get_file_as_string 'path/to/my_policy.txt'
# print out the string
puts #policy

Related

Move line from one text file to another

I have a list of names (names.txt) separated by line. After I loop through each line, I'd like to move it to another file (processed.txt).
My current implementation to loop through each line:
open("names.txt") do |csv|
csv.each_line do |line|
url = line.split("\n")
puts url
# Remove line from this file amd move it to processed.txt
end
end
def readput
#names = File.readlines("names.txt")
File.open("processed.txt", "w+") do |f|
f.puts(#names)
end
end
You can do it like this:
File.open('processed.txt', 'a') do |file|
open("names.txt") do |csv|
csv.each_line do |line|
url = line.chomp
# Do something interesting with url...
file.puts url
end
end
end
This will result in processed.txt containing all of the urls that were processed with this code.
Note: Removing the line from names.txt is not practical using this method. See How do I remove lines of data in the middle of a text file with Ruby for more information. If this is a real goal of this solution, it will be a much larger implementation with some design considerations that need to be defined.

Ruby, Tempfile, CSV

I have the below resque job that produces a csv file and sends it to a mailer. I want to validate that the csv file has data so I do not email blank files. For some reason, when I write a method outside of the perform method, it will not work. For example, the below code will print invalid when I know the csv file has data on the first line. If I uncomment the line below ensure it works properly, however I want to extract this checking of the file into a separate method. Is this correct?
class ReportJob
#queue = :report_job
def self.perform(application_id, current_user_id)
user = User.find(current_user_id)
client_application = Application.find(client_application_id)
transactions = application.transactions
file = Tempfile.open(["#{Rails.root}/tmp/", ".csv"]) do |csv|
begin
csv_file = CSV.new(csv)
csv_file << ["Application", "Price", "Tax"]
transactions.each do |transaction|
csv_file << [application.name, transaction.price, transaction.tax]
end
ensure
ReportJob.email_report(user.email, csv_file)
#ReportMailer.send_report(user.email, csv_file).deliver
csv_file.close(unlink=true)
end
end
end
def self.email_report(email, csv)
array = csv.to_a
if array[1].blank?
puts "invalid"
else
ReportMailer.send_report(email, csv).deliver
end
end
end
You should invoke your method as such:
ReportJob.email_report(email, csv)
Otherwise, get rid of the self in:
def self.email_report(email, csv)
# your implementation here.
end
and define your method as follows:
def email_report(email, csv)
# your implementation.
end
This is something that we call Class Methods and Instance Methods.

How to simplify this Rails code -- gets first line from file, parses it and downcases each element

fields = CSV.parse(File.open(filename).first)[0]
fields.each_with_index do |field, i|
fields[i] = field.downcase
end
I want to get the first line from the line, parse it as CSV and make each element lowercase.
This code seems too redundant to me. Any suggestions?
You can make the looping stuff a bit more concise if you wish:
fields.map!(&:downcase)
or even:
fields = CSV.parse(File.open(filename).first)[0].map(&:downcase)
I think you're leaving a file handle hanging there too so you might want to try something like:
fields = []
File.open(filename) do |f|
fields = CSV.parse(f.readline)[0].map(&:downcase)
end
I don't think there's anything wrong with what you have but you could say this:
fields = CSV.parse(File.open(filename, 'r').first).first.map(&:downcase)
Or you could make it easier to read with some methods:
def first_line_of(filename)
File.open(filename, 'r').first
end
def csv_to_array(string)
CSV.parse(string).first
end
def downcase(a)
a.map(&:downcase)
end
fields = downcase csv_to_array first_line_of filename

How to edit docx with nokogiri and rubyzip

I'm using a combination of rubyzip and nokogiri to edit a .docx file. I'm using rubyzip to unzip the .docx file and then using nokogiri to parse and change the body of the word/document.xml file but ever time I close rubyzip at the end it corrupts the file and I can't open it or repair it. I unzip the .docx file on desktop and check the word/document.xml file and the content is updated to what I changed it to but all the other files are messed up. Could someone help me with this issue? Here is my code:
require 'rubygems'
require 'zip/zip'
require 'nokogiri'
zip = Zip::ZipFile.open("test.docx")
doc = zip.find_entry("word/document.xml")
xml = Nokogiri::XML.parse(doc.get_input_stream)
wt = xml.root.xpath("//w:t", {"w" => "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}).first
wt.content = "New Text"
zip.get_output_stream("word/document.xml") {|f| f << xml.to_s}
zip.close
I ran into the same corruption problem with rubyzip last night. I solved it by copying everything to a new zip file, replacing files as necessary.
Here's my working proof of concept:
#!/usr/bin/env ruby
require 'rubygems'
require 'zip/zip' # rubyzip gem
require 'nokogiri'
class WordXmlFile
def self.open(path, &block)
self.new(path, &block)
end
def initialize(path, &block)
#replace = {}
if block_given?
#zip = Zip::ZipFile.open(path)
yield(self)
#zip.close
else
#zip = Zip::ZipFile.open(path)
end
end
def merge(rec)
xml = #zip.read("word/document.xml")
doc = Nokogiri::XML(xml) {|x| x.noent}
(doc/"//w:fldSimple").each do |field|
if field.attributes['instr'].value =~ /MERGEFIELD (\S+)/
text_node = (field/".//w:t").first
if text_node
text_node.inner_html = rec[$1].to_s
else
puts "No text node for #{$1}"
end
end
end
#replace["word/document.xml"] = doc.serialize :save_with => 0
end
def save(path)
Zip::ZipFile.open(path, Zip::ZipFile::CREATE) do |out|
#zip.each do |entry|
out.get_output_stream(entry.name) do |o|
if #replace[entry.name]
o.write(#replace[entry.name])
else
o.write(#zip.read(entry.name))
end
end
end
end
#zip.close
end
end
if __FILE__ == $0
file = ARGV[0]
out_file = ARGV[1] || file.sub(/\.docx/, ' Merged.docx')
w = WordXmlFile.open(file)
w.force_settings
w.merge('First_Name' => 'Eric', 'Last_Name' => 'Mason')
w.save(out_file)
end
I stumbled accross the post and know nothing about ruby or nokogiri but ...
It looks like you are reziping the new content incorrectly.
I don't know about rubyzip, but you need a way to tell it to update the entry word/document.xml
and then resave/rezip the file.
It looks like you are just overwriting the entry with new data wich of course is going to be a different size and totally screw up the rest of the zip file.
I give an example for excel in this post Parse text file and create an excel report
which may be of use even though i am using a different zip library and VB (Im still doing exactly what you are trying to do, my code is about half way down)
here is the part that applies
Using z As ZipFile = ZipFile.Read(xlStream.BaseStream)
'Grab Sheet 1 out of the file parts and read it into a string.
Dim myEntry As ZipEntry = z("xl/worksheets/sheet1.xml")
Dim msSheet1 As New MemoryStream
myEntry.Extract(msSheet1)
msSheet1.Position = 0
Dim sr As New StreamReader(msSheet1)
Dim strXMLData As String = sr.ReadToEnd
'Grab the data in the empty sheet and swap out the data that I want
Dim str2 As XElement = CreateSheetData(tbl)
Dim strReplace As String = strXMLData.Replace("<sheetData/>", str2.ToString)
z.UpdateEntry("xl/worksheets/sheet1.xml", strReplace)
'This just rezips the file with the new data it doesnt save to disk
z.Save(fiRet.FullName)
End Using
According to the official Github documentation, you should Use write_buffer instead open. There's also a code example at the link.

code refactoring using pathname and writing to a file

I am using ruby on rails. Below given code works. However I was wondering if it can be written better.
# Usage: write 'hello world' to tmp/hello.txt file
# Util.write_to_file('hello world', 'a+', 'tmp', 'hello.txt')
def self.write_to_file(data, mode, *args)
input = args
filename = input.pop
dir = Rails.root.join(*input).cleanpath.to_s
FileUtils.mkdir_p(dir)
file = File.join(dir, filename)
File.open(file, mode) {|f| f.puts(data) }
end
How often are you going to be changing the mode? If not very often, I'd put it directly in the method, and do the rest like so:
def self.write_to_file(data, *args)
file = Rails.root.join(*args)
FileUtils.mkdir_p(file.dirname)
File.open(file, "a+") { |f| f.puts(data) }
end
You can just leverage the existing API instead of having to do all the dirty work yourself which cleans things up a lot:
def self.write_to_file(data, mode, *path)
path = File.expand_path(File.join(path.flatten), Rails.root)
FileUtils.mkdir_p(File.dirname(path))
File.open(path, mode) do |fh|
fh.print(data)
end
end
There's a few things to note here.
File.expand_path will resolve any "../" parts to the path.
File.dirname is great at determining the directory of an arbitrary file path.
Use File#print to write data to a file. File#puts appends a linefeed.

Resources