Skip to content

Commit

Permalink
Add convenience methods to Spreadsheet to access Sheets (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouball committed Oct 16, 2023
1 parent 9634a88 commit 8fd7ec5
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 0 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,13 @@ sheets_service, spreadsheet, and sheet objects separately.

The `sheets_service` attribute is added and is set by `SheetsService#get_spreadsheet`.

Convenience methods for getting sheets within the spreadsheet are added:

* `sheet(id_or_title)`: returns the sheet matching the id or title given
* `sheet_id(title)`: returns the ID for the sheet matching the title given
* `each_sheet(ids_or_titles)`: enumerates the sheets within a spreadsheet matching
the given IDs or titles.

#### Sheet Extensions

The `sheets_service` and `spreadsheet` attributes are added. Both are set when the
Expand Down
80 changes: 80 additions & 0 deletions lib/sheets_v4/google_extensions/spreadsheet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,86 @@ module Spreadsheet
#
# @return [Google::Apis::SheetsV4::SheetsService]
attr_reader :sheets_service

# Return the matching sheet object or nil
#
# If `id_or_title` is an Integer, it is assumed to be the sheet ID. Otherwise,
# it is assumed to be the sheet title.
#
# @example Get the sheet whose title is 'Sheet1'
# sheet = spreadsheet.sheet('Sheet1')
#
# @example Get the sheet whose title is '2023-03-15'
# date = Date.new(2023, 3, 15)
# sheet = spreadsheet.sheet(date)
#
# @example Get the sheet whose ID is 123456
# sheet = spreadsheet.sheet(123456)
#
# @param id_or_title [Integer, #to_s] the ID or title of the sheet to return
#
# @return [Google::Apis::SheetsV4::Sheet, nil]
#
def sheet(id_or_title)
if id_or_title.is_a?(Integer)
sheets.find { |sheet| sheet.properties.sheet_id == id_or_title }
else
title = id_or_title.to_s
sheets.find { |sheet| sheet.properties.title == title }
end
end

# Return the ID of the matching sheet
#
# @example
# id = spreadsheet.sheet_id('Sheet1')
#
# @param id_or_title [Integer, #to_s] the ID or title of the sheet
#
# @return [Integer]
#
def sheet_id(id_or_title)
sheet(id_or_title)&.properties&.sheet_id
end

# Iterate over sheets in a spreadsheet
#
# If `ids_or_titles` is not given or is nil, all sheets are enumerated. Otherwise,
# only the sheets whose IDs or titles are in `sheets` are enumerated.
#
# @example Enumerate all sheets
# spreadsheet.each_sheet { |sheet| puts sheet.properties.title }
#
# @example Enumerate sheets whose IDs are 123456 and 789012
# sheets = [123456, 789012]
# spreadsheet.each_sheet(sheets).with_index do |sheet, index|
# puts "#{index}: #{sheet.properties.title} (#{sheet.properties.sheet_id})"
# end
#
# @param ids_or_titles [Array<Integer, #to_s>] an array of sheet IDs and/or titles
#
# An Integer in this array is match to the sheet ID. Anything else is matched
# to the sheet title after calling `#to_s` on it.
#
# @return [void]
#
# @yield each matching sheet
# @yieldparam [Google::Apis::SheetsV4::Sheet] the matching sheet
#
# @raise [RuntimeError] if one of the sheets does not exist
# @raise [RuntimeError] if a block is not given
#
def each_sheet(ids_or_titles = sheets.map { |sheet| sheet.properties.sheet_id }, &block)
return enum_for(:each_sheet, ids_or_titles) unless block

matching_sheets = ids_or_titles.map do |id_or_title|
sheet(id_or_title) || raise("Could not find sheet '#{id_or_title}'")
end

matching_sheets.each { |sheet| block[sheet] }

self
end
end
end
end
174 changes: 174 additions & 0 deletions spec/sheets_v4/google_extensions/spreadsheet_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# frozen_string_literal: true

# Copyright (c) 2022 Yahoo

require 'sheets_v4/google_extensions'

RSpec.describe SheetsV4::GoogleExtensions::Spreadsheet do
let(:spreadsheet_class) do
Class.new do
attr_reader :spreadsheet_id
attr_reader :sheets

def initialize(spreadsheet_id, sheets)
@spreadsheet_id = spreadsheet_id
@sheets = sheets
end

prepend SheetsV4::GoogleExtensions::Spreadsheet
end
end

let(:sheet_class) do
Class.new do
def initialize(id, title)
@properties = Struct.new(:sheet_id, :title).new(id, title)
end
attr_reader :properties
end
end

let(:sheet1) { sheet_class.new(1, 'Sheet1') }
let(:sheet2) { sheet_class.new(2, 'Sheet2') }
let(:sheets) { [sheet1, sheet2] }
let(:spreadsheet_id) { 'spreadsheet_id' }
let(:spreadsheet) { spreadsheet_class.new(spreadsheet_id, sheets) }

describe '#sheet' do
subject { spreadsheet.sheet(id_or_title) }

context 'when the spreadsheet has one sheet whose id is 1 and title is "Sheet1"' do
let(:sheets) { [sheet1] }

context 'when requesting the sheet whose id is 1' do
let(:id_or_title) { 1 }
it 'should return the matching sheet' do
expect(subject).to eq(sheet1)
end
end

context 'when requesting the sheet whose title is "Sheet1"' do
let(:id_or_title) { 'Sheet1' }
it 'should return the matching sheet' do
expect(subject).to eq(sheet1)
end
end

context 'when requesting the sheet whose id is 2' do
let(:id_or_title) { 2 }
it 'should return nil' do
expect(subject).to be_nil
end
end

context 'when requesting the sheet whose title is "Sheet2"' do
let(:id_or_title) { 'Sheet2' }
it 'should return nil' do
expect(subject).to be_nil
end
end
end
end

describe '#sheet_id' do
subject { spreadsheet.sheet_id(id_or_title) }

context 'when the spreadsheet has one sheet whose id is 1 and title is "Sheet1"' do
let(:sheets) { [sheet1] }

context 'when requesting the sheet whose id is 1' do
let(:id_or_title) { 1 }
it 'should return the matching sheet' do
expect(subject).to eq(1)
end
end

context 'when requesting the sheet whose title is "Sheet1"' do
let(:id_or_title) { 'Sheet1' }
it 'should return the matching sheet' do
expect(subject).to eq(1)
end
end

context 'when requesting the sheet whose id is 2' do
let(:id_or_title) { 2 }
it 'should return nil' do
expect(subject).to be_nil
end
end

context 'when requesting the sheet whose title is "Sheet2"' do
let(:id_or_title) { 'Sheet2' }
it 'should return nil' do
expect(subject).to be_nil
end
end
end
end

describe '#each_sheet' do
context 'when no sheets are given' do
it 'should iterate over all sheets' do
ids = []
spreadsheet.each_sheet { |sheet| ids << sheet.properties.sheet_id }
expect(ids).to eq([1, 2])
end
end

context 'when sheets are given' do
it 'should iterate over the matching sheets in the order given' do
ids = []
spreadsheet.each_sheet(%w[Sheet1 Sheet2]) { |sheet| ids << sheet.properties.sheet_id }
expect(ids).to eq([1, 2])

ids = []
spreadsheet.each_sheet(%w[Sheet2 Sheet1]) { |sheet| ids << sheet.properties.sheet_id }
expect(ids).to eq([2, 1])
end
end

context 'when given a subset of sheets' do
it 'should iterate over ONLY the matching sheets' do
ids = []
spreadsheet.each_sheet(%w[Sheet1]) { |sheet| ids << sheet.properties.sheet_id }
expect(ids).to eq([1])
end
end

context 'when given a sheet that does not exist' do
it 'should raise a runtime error without calling the block' do
ids = []
begin
spreadsheet.each_sheet(%w[Sheet1 Sheet3]) { |sheet| ids << sheet.properties.sheet_id }
rescue RuntimeError
runtime_error_raised = true
end
expect(runtime_error_raised).to eq(true)
expect(ids).to eq([])
end
end

context 'when given an empty array' do
it 'should not call the block' do
blocked_called = false
spreadsheet.each_sheet(%w[]) { |_sheet| blocked_called = true }
expect(blocked_called).to eq(false)
end
end

context 'when called without a block' do
it 'should return an Enumerator' do
expect(spreadsheet.each_sheet).to be_kind_of(Enumerator)
end

it 'should allow calling other Enumerator functions like with_index' do
result = []
enumerator = spreadsheet.each_sheet
enumerator.with_index do |sheet, index|
result << [sheet.properties.title, index]
end
expect(result).to eq([['Sheet1', 0], ['Sheet2', 1]])
end
end
end
end

0 comments on commit 8fd7ec5

Please sign in to comment.