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

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

Related

Ruby replace if block with guard

I've got a service object which creates the CSV file from assigned data. The call method is pretty simple:
def initialize(data)
#data = data
end
def call
CSV.generate(headers: true, col_sep: ';') do |csv|
csv << csv_headers
data.uniq.each do |contract|
next if contract.transient
payment_details = [
next_payment_date(contract),
I18n.t("contracts.interval_options.#{contract.recurring_transaction_interval&.name}"),
]
csv << payment_details
end
end
end
private
def next_payment_date(contract)
if contract.upcoming_installment.nil?
I18n.t('tables.headers.no_next_payment_date')
else
contract.upcoming_installment.transaction_date.to_s
end
end
It works well but I don't think next_payment_date is really fancy if block, I'm wondering is it possible to replace it with some guard instead?
Because of rubocop I cannot use:
contract.upcoming_installment.nil? ? I18n.t('tables.headers.no_next_payment_date') : contract.upcoming_installment.transaction_date.to_s
In my opinion, the method looks fine as it is. Having better readability is a profitable trade-off for increasing LOCs or introducing methods. That said, there are ways where you can make it an one liner if you really fancy.
contract.upcoming_installment&.transaction_date || I18n.t('tables.headers.no_next_payment_date')
Ruby's safe navigation &. will return nil if upcoming_installment is nil which should fallback to the I18n.

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/Rails Iterate through array and save to db?

I want to put each string from #enc into each field of column_name as a value
#enc=["hUt7ocoih//kFpgEizBowBAdxqqbGV1jkKVipVJwJnPGoPtTN16ZAJvW9tsi\n3inn\n", "wGNyaoEZ09jSg+/IclWFGAXzwz5lXLxJTUKqCFIiOy3ZXRgdwFUsNf/75R2V\nZm83\n", "MPq3KSzDzLvTeYh+h00HD+5FAgKoNksykJhzROVZWbIJ36WNoBgkSoicJ5wx\nog0g\n"]
Model.all.each do |row|
encrypted = #enc.map { |i| i}
row.column_name = encrypted
row.save!
end
My code puts all strings from array #enc into a single field?
I do not want that.
Help
Rails by default won't allow mass assignment. You have to whitelist parameters you want permitted. Have you tried doing something like the following?
#enc.each do |s|
cparams = create_params
cparams[:column_name] = s
Model.create(cparams)
end
def create_params
params.permit(:column_name)
end
You will need to specify the column names you are saving to. By setting each column separately you can also avoid mass-assignment errors:
#enc=["hUt7ocoih//kFpgEizBowBAdxqqbGV1jkKVipVJwJnPGoPtTN16ZAJvW9tsi\n3inn\n", "wGNyaoEZ09jSg+/IclWFGAXzwz5lXLxJTUKqCFIiOy3ZXRgdwFUsNf/75R2V\nZm83\n", "MPq3KSzDzLvTeYh+h00HD+5FAgKoNksykJhzROVZWbIJ36WNoBgkSoicJ5wx\nog0g\n"]
model = Widget.new
column_names = [:column1, :column2, :column3]
#enc.each_with_index do |s, i|
model[column_names[i]] = s
end
model.save
I think you are looking for something like this:
#enc.each do |str|
m = Model.new
m.column_name = str
m.save
end

Rails personal class Each routine

I'm kind of a newbie in some areas of ruby and rails. So I I'm writing a class to read excel depending on the extension and return the row in a each routine. Something like this:
class ExcelRead
(dependencies)
def initialize(path, sheet_n = 0)
type = File.extname(path)
if type == JitExcelRead::XLS
Spreadsheet.client_encoding = 'UTF-8'
book = Spreadsheet.open path
book_sheet = book.worksheet sheet_n
elsif type == JitExcelRead::XLSX
book = Creek::Book.new path
book_sheet = book.sheets[sheet_n]
end
#book = book
#book_sheet = book_sheet
#book_rows = book_sheet.rows
#path = path
#type = type
end
end
So this means that I call on my application
xls = ExcelRead.new(uploaded_file.filename_path)
and everything runs smooth. I have the objects I need at my disposal. My problem now is how to iterate through them. I thought that adding a method to may class like this
def each
binding.pry
end
and calling it normally on my app like so
xls.book_rows.each do |row|
end
would make me enter that code, but not really...
help?
If you added a each method to your ExcelRead class, and you create an instance of this class called xls, then you have to access it using xls.each, not xls.book_rows.each.
Using the former, you are calling the each method from the Enumerator, as book_rows is a collection.
I can only guess that you want a custom way to iterate your book_row, so i think something like this should be what you are trying to achieve:
def iterate
self.book_rows.each do |br|
# do stuff
end
end
And you call it like:
xls.iterate
But this is only a wild guess.

metaprogramming for params

How can I update these very similar text fields in a less verbose way? The text fields below are named as given - I haven't edited them for this question.
def update
company = Company.find(current_user.client_id)
company.text11 = params[:content][:text11][:value]
company.text12 = params[:content][:text12][:value]
company.text13 = params[:content][:text13][:value]
# etc
company.save!
render text: ""
end
I've tried using send and to_sym but no luck so far...
[:text11, :text12, :text13].each do |s|
company.send("#{s}=".to_sym, params[:content][s][:value])
end
If they are all incremental numbers, then:
11.upto(13).map{|n| "text#{n}".to_sym}.each do |s|
company.send("#{s}=".to_sym, params[:content][s][:value])
end
I'd consider first cleaning up the params, then move onto dynamically assigning attributes. A wrapper class around your params would allow you to more easily unit test this code. Maybe this helps get you started.
require 'ostruct'
class CompanyParamsWrapper
attr_accessor :text11, :text12, :text13
def initialize(params)
#content = params[:content]
content_struct = OpenStruct.new(#content)
self.text11 = content_struct.text11[:value]
self.text12 = content_struct.text12[:value]
self.text13 = content_struct.text13[:value]
end
end
# Company model
wrapper = CompanyParamsWrapper.new(params)
company.text11 = wrapper.text11
# now easier to use Object#send or other dynamic looping

Resources