What Is the Second Parameter/Argument to CSV.Open( ) in Ruby

What is the second parameter/argument to CSV.open( ) in ruby?

From the IO Open Mode documentation:

"r" Read-only, starts at beginning of file (default mode).

"r+" Read-write, starts at beginning of file.

"w" Write-only, truncates existing file
to zero length or creates a new file for writing.

"w+" Read-write, truncates existing file to zero length
or creates a new file for reading and writing.

"a" Write-only, starts at end of file if file exists,
otherwise creates a new file for writing.

"a+" Read-write, starts at end of file if file exists,
otherwise creates a new file for reading and
writing.

CSV code in Ruby

Reading the comments, I think you should first make yourself familiar with the basic building blocks and the syntax of Ruby.

I know, in the beginning I had tons of similar questions.
I found a very basic and playful approach to learn the basics of the Ruby programming language in this book.

To answer your question:

CSV.foreach(csv_file_name, header: true) is a method call to CSV. The method foreach in this case receives two arguments.

  1. csv_file_name is a variable that is being defined as the first argument of the outer method read_in_csv_data.

    So if you call CsvReader.new.read_in_csv_data("file.csv") it will pass the string "file.csv" down to CSV.foreach("file.csv", ....

  2. header: true is basically a Hash, but this style is commonly known as named arguments. Very useful and readable if you want to pass down options to a method that are very specific.

    How does CSV.foreach receive header: true?
    That is easy to find out, open up a $ irb session on your local machine, and define the following method:

    def foo(arg1, arg2)
    p arg1
    p arg2
    nil
    end

    Now play around by calling foo with different arguments.

    > foo("string", 123)
    "string"
    123
    => nil
    > foo("string", named: "argument")
    "string"
    {:named=>"argument}
    => nil
    > foo("string", { named: "argument" })
    "string"
    {:named=>"argument}
    => nil

    Doing this you might grasp how Ruby handles named arguments or hash arguments. As you see, the latter examples above show that you can specify a Hash without {} as the last argument on any method in Ruby.

    header: true represents a Hash, ie {:header => true}.

    What does header: true trigger?
    This has to be read up in the documentation for CSV::foreach. You you do a Google search for "ruby csv foreach" you will quickly find a documentation. Here is a direct link to CSV::foreach.
    There you can read "The options parameter can be anything ::new understands.". So you gotta look for the documentation of CSV::new. This leads you to a nice description of each option you can pass to CSV::new or CSV::foreach.

    What does the documentation say?

    :headers If set to :first_row or true, the initial row of the CSV file
    will be treated as a row of headers. If set to an Array, the contents
    will be used as the headers. If set to a String, the String is run
    through a call of ::parse_line with the same :col_sep, :row_sep, and
    :quote_char as this instance to produce an Array of headers. This
    setting causes #shift to return rows as CSV::Row objects instead of
    Arrays and #read to return CSV::Table objects instead of an Array of
    Arrays.

Your other questions:

You are welcome to ask questions, but I also think it would be very good for you to get someone to help you directly. Maybe find yourself a local Ruby user group. People there are usually very friendly and helpful.

Having said that, I will now explain what row["ISBN"] means:

CSV::foreach is a method that yields a block. This block is somewhat similar to a function, and per definition of CSV::foreach it lets you do something with each row of the csv file.

row itself is just a variable name defined in the block arguments (ie |row|).

Maybe its time for you to open up another $ irb and play around with blocks yourself:

def foo
yield 123
end

Try calling foo the same way as you do CSV::foreach, just that you print the output.

> foo do |row| puts row end
123
=> nil
> foo { |arg1| puts arg1 }
123
=> nil

As you see yield 123 is passing 123 as the first argument of the block. You take the first argument in a variable (eg row or arg1) and you can do something with it in the block. Also note that you can create these blocks with do |arg1| ... end but also { |arg1| ... }.

So what is it with this ["ISBN"]??

row itself is just a variable, but is also an object so you can call methods on it. In Ruby you might have already encountered a hash.
The contents of a hash in Ruby can be accessed by calling [] on the hash.

The same way each row of the CSV is represented. Just that each row (since you told CSV to use :headers) is being represented as a hash-like object.

If you omit :headers each row will be an array of arrays.

Maybe its time for you to open up an $ irb and play around with CSV directly. For that create yourself a simple csv file like the following, and save it to disk:

id,name
1,Overbryd
2,Boniface
3,Poignant

Then, open up a $ irb -r csv.

> CSV.foreach("test.csv") { |r| p r }
["id", "name"]
["1", "Overbryd"]
["2", "Boniface"]
["3", "Poignant"]
=> nil
> CSV.foreach("test.csv", headers: true) { |r| p r }
#<CSV::Row "id":"1" "name":"Overbryd">
#<CSV::Row "id":"2" "name":"Boniface">
#<CSV::Row "id":"3" "name":"Poignant">
=> nil
> CSV.foreach("test.csv", headers: true) do |row|
puts row["id"]
end
1
2
3
=> nil

Also, please accept one of the given answers. People take time to answer your questions, their only reward is if you accept one of their answers.

Ruby CSV Open - Wrong number of arguments in Ruby 3.0

In Ruby 3.0, positional arguments and keyword arguments was separated, with deprecation in Ruby 2.7. That means, that the Ruby 2.7 deprecation warning: Using the last argument as keyword parameters is deprecated, ie. using a hash as argument for **options is no longer supported.

You'll have to call it either as:

# Manual spreading hash
CSV.open(csv_path, "wb", **{:col_sep => ";"}) do |csv|
...
end

# Or as Keyword argument

CSV.open(csv_path, "wb", :col_sep => ";") do |csv|
...
end

EDIT: See also https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/

What are the Ruby File.open modes and options?

In Ruby IO module documentation, I suppose.

Mode |  Meaning
-----+--------------------------------------------------------
"r" | Read-only, starts at beginning of file (default mode).
-----+--------------------------------------------------------
"r+" | Read-write, starts at beginning of file.
-----+--------------------------------------------------------
"w" | Write-only, truncates existing file
| to zero length or creates a new file for writing.
-----+--------------------------------------------------------
"w+" | Read-write, truncates existing file to zero length
| or creates a new file for reading and writing.
-----+--------------------------------------------------------
"a" | Write-only, starts at end of file if file exists,
| otherwise creates a new file for writing.
-----+--------------------------------------------------------
"a+" | Read-write, starts at end of file if file exists,
| otherwise creates a new file for reading and
| writing.
-----+--------------------------------------------------------
"b" | Binary file mode (may appear with
| any of the key letters listed above).
| Suppresses EOL <-> CRLF conversion on Windows. And
| sets external encoding to ASCII-8BIT unless explicitly
| specified.
-----+--------------------------------------------------------
"t" | Text file mode (may appear with
| any of the key letters listed above except "b").

What does rb:bom|utf-8 mean in CSV.open in Ruby?

When reading a text file in Ruby you need to specify the encoding or it will revert to the default, which might be wrong.

If you're reading CSV files that are BOM encoded then you need to do it that way.

Pure UTF-8 encoding can't deal with the BOM header so you need to read it and skip past that part before treating the data as UTF-8. That notation is how Ruby expresses that requirement.

Ruby CSV - Create a file and edit a file in one .rb and then open and edit it starting where the last .rb left off in another .rb?

The second argument of the open method is the write mode.
See here: https://stackoverflow.com/a/17866433/9595653

And also refer to the documentation for CSV here:
https://ruby-doc.org/stdlib-2.6.1/libdoc/csv/rdoc/CSV.html

You will want to change the option to "a"

Saying there are 0 arguments when I have 2? Trying to open a CSV file to write into

I believe the problem is with Dir.foreach, not CSV.open.

You need to supply a directory to foreach as an argument. That's why you are getting the missing argument error.

Try:

Dir.foreach('/path/to_my/directory') do |current_file|

I think the open that is referenced in the error message is when Dir is trying to find the directory to open in order to get the file list for foreach.

Writing data into a CSV file by two different CSV files

Cleaning up ecode.csv made it more challenging, but here is what I came up with:

In case, data.csv and ecode.csv are matched by row numbers:

require 'csv'

data = CSV.read('data.csv', headers: true).to_a
headers = data.shift << 'eppocode'

double_quoted_ecode = CSV.read('ecode.csv')
ecodeIO = StringIO.new
ecodeIO.puts double_quoted_ecode.to_a
ecodeIO.rewind
ecode = CSV.parse(ecodeIO, headers: true)

CSV.open('plantas.csv', 'w+') do |plantas|
plantas << headers
data.each.with_index do |row, idx|
planta = row + [ecode['code'][idx]]
plantas << planta
end
end

Using your example files, this gives you the following plantas.csv:

id,type,code,name_es,name_ca,name_en,latin_name,customer_id,eppocode
7205,DunSpecies,NULL,0,0,0,"",11630,LEECO
7437,DunSpecies,NULL,0,Xicoira,0,"",5273,LEECO

In case, entries are matched by data.csv's id and ecode.csv's identifier:

require 'csv'

data = CSV.read('data.csv', headers: true)
headers = data.headers << 'eppocode'

double_quoted_ecode = CSV.read('ecode.csv')
ecodeIO = StringIO.new
ecodeIO.puts double_quoted_ecode.to_a
ecodeIO.rewind
ecode = CSV.parse(ecodeIO, headers: true)

CSV.open('plantas.csv', 'w+') do |plantas|
plantas << headers
data.each do |row|
id = row['id']
ecode_row = ecode.find { |entry| entry['identifier'] == id } || {}
planta = row << ecode_row['code']
plantas << planta
end
end

I hope you find this helpful.



Related Topics



Leave a reply



Submit