I create this code, using this reference https://developers.google.com/drive/api/quickstart/ruby
class Drive
def initialize
#drive_service = Google::Apis::DriveV3::DriveService.new
#drive_service.client_options.application_name = APPLICATION_NAME
#drive_service.authorization = authorize
end
def create_file
data = { 'name': "My new Sheet #{Time.now.strftime('%d/%m/%Y %H:%M')}",
'mimeType': 'application/vnd.google-apps.spreadsheet' }
#drive_service.create_file(data).execute
end
def share_file
"to be filled"
end
def list_files
response = #drive_service.list_files(page_size: 10, fields: 'nextPageToken, files(id, name)')
puts 'Files:'
puts 'No files found' if response.files.empty?
response.files.each do |file|
puts "#{file.name} (#{file.id})"
end
end
end
The method list_files works well, but the create_file return me this error:
Traceback (most recent call last):
2: from quickstart.rb:79:in `<main>'
1: from quickstart.rb:60:in `create_file'
/home/vagrant/.rvm/gems/ruby-2.7.2/gems/google-api-client-0.53.0/generated/google/apis/drive_v3/service.rb:895:in `create_file': unknown keywords: :name, :mimeType (ArgumentError)
I created it based on this create method reference: https://googleapis.dev/ruby/google-api-client/latest/Google/Apis/DriveV3/DriveService.html#create_file-instance_method
but I still can't get it to work, what could be wrong?
I tried changing the creation to: #drive_service.create_file(file_object = data).execute
Let's compare your code,
data = { 'name': "My new Sheet ...
#drive_service.create_file(data).execute
vs. the documented method signature.
`create_file(file_object = nil, enforce_single_parent: nil, ignore_default_visibility: nil, include_permissions_for_view: nil, keep_revision_forever: nil, ocr_language: nil, supports_all_drives: nil, supports_team_drives: nil, use_content_as_indexable_text: nil, fields: nil, quota_user: nil, user_ip: nil, upload_source: nil, content_type: nil, options: nil) {|result, err| ... } ⇒ Google::Apis::DriveV3::File`
- file_object (Google::Apis::DriveV3::File) (defaults to: nil)
The first argument should be an instance of Google::Apis::DriveV3::File, but you are passing a Hash.
According to this documentation:
When supplying hashes for request objects. If it is the last argument
to a method, some versions of Ruby will interpret the hash as keyword
arguments. To prevent this, appending an empty hash as an extra
parameter will avoid misinterpretation.
file = {id: '123', title: 'My document', labels: { starred: true }}
file = drive.create_file(file) # Raises ArgumentError: unknown keywords: id, title, labels
file = drive.create_file(file, {}) # Returns a Drive::File instance
In your code, just add {} as 2nd parameter for create_file.
From:
#drive_service.create_file(data)
To:
#drive_service.create_file(data, {})
Let me know if this works on your end.
Related
I’m using the Ruby SDK aws-sdk-dynamodb with Ruby 2.5 for an AWS Lambda function that saves an item to an AWS DynamoDB table.
I can successfully save an item with this code:
def save!
hash = {
table_name: ‘my-table’,
item: {
message_sid: '123456',
created_at: Time.now.to_s
}
}
dynamo = Aws::DynamoDB::Client.new(region: ‘us-east-1’)
dynamo.put_item(hash)
puts 'item successfully saved'
true
rescue => error
puts "Unable to save item: #{error}: #{error.message}"
false
end
I get an error “no such member :message_sid” when I use this code:
def save!
dynamoDB = Aws::DynamoDB::Resource.new(region: ‘us-east-1’)
table = dynamoDB.table(‘my-table’)
hash = { message_sid: '123456', created_at: Time.now.to_s }
table.put_item(hash)
puts 'item successfully saved'
true
rescue => error
puts "Unable to save item: #{error}: #{error.message}"
false
end
I haven’t found any DynamoDB documentation for the error “no such member”. Why does the second example fail?
Admittedly the error message is not very helpful but a close reading of the example documentation reveals that DynamoDB expects the key item: when the method put_item is called on a table object. So this code will work:
def save!
dynamoDB = Aws::DynamoDB::Resource.new(region: ‘us-east-1’)
table = dynamoDB.table(‘my-table’)
hash = { message_sid: '123456', created_at: Time.now.to_s }
table.put_item(item: hash)
puts 'item successfully saved'
true
rescue => error
puts "Unable to save item: #{error}: #{error.message}"
false
end
Specifically you should use table.put_item(item: hash) not table.put_item(hash).
On a Rails project, I am gathering a hash with 10-15 key-value pairs, and passing it to a class (service object) for instantiation. The object properties should be set from the values in the hash except when there is no value (or nil). In this case, the property would desirably get set to a default value.
Instead of checking whether every value in the hash is not nil before creating an object, I would like to find a more efficient way of doing this.
I'm trying to use named parameters with default values. I don't know if this makes sense, but I would like to use the default value when the parameter is called with nil. I created a test for this functionality:
class Taco
def initialize(meat: "steak", cheese: true, salsa: "spicy")
#meat = meat
#cheese = cheese
#salsa = salsa
end
def assemble
"taco with: ##meat + ##cheese + ##salsa"
end
end
options1 = {:meat => "chicken", :cheese => false, :salsa => "mild"}
chickenTaco = Taco.new(options1)
puts chickenTaco.assemble
# => taco with: chicken + false + mild
options2 = {}
defaultTaco = Taco.new(options2)
puts defaultTaco.assemble
# => taco with: steak + true + spicy
options3 = {:meat => "pork", :cheese => nil, :salsa => nil}
invalidTaco = Taco.new(options3)
puts invalidTaco.assemble
# expected => taco with: pork + true + spicy
# actual => taco with: pork + +
If you want to follow a Object-Oriented approach, you could isolate your defaults in a separate method and then use Hash#merge:
class Taco
def initialize (args)
args = defaults.merge(args)
#meat = args[:meat]
#cheese = args[:cheese]
#salsa = args[:salsa]
end
def assemble
"taco with: #{#meat} + #{#cheese} + #{#salsa}"
end
def defaults
{meat: 'steak', cheese: true, salsa: 'spicy'}
end
end
Then following the suggestion by #sawa (thanks), use Rails' Hash#compact for your input hashes that have explicitly defined nil values and you will have the following output:
taco with: chicken + false + mild
taco with: steak + true + spicy
taco with: pork + true + spicy
EDIT:
If you do not want to use Rails' wonderful Hash#compact method, you can use Ruby's Array#compact method. Replacing the first line within the initialize method to:
args = defaults.merge(args.map{|k, v| [k,v] if v != nil }.compact.to_h)
Once you pass a value with a named parameter, access to the default value for that parameter is gone for that method call.
You either have to (i) assign the default value not in the method profile but in the method body as in sagarpandya82's answer, or (ii) remove the nil values before passing the arguments to the method like this using Rails' Hash#compact:
options3 = {:meat => "pork", :cheese => nil, :salsa => nil}
invalidTaco = Taco.new(options3.compact)
I don't think keyword arguments would be appropriate in your case. It seems a Hash is a better fit.
class Taco
attr_accessor :ingredients
def initialize(ingredients = {})
#ingredients = ingredients
end
def assemble
"taco with: #{ingredients[:meat]} + #{ingredients[:cheese]} + #{ingredients[:salsa]}"
end
end
You can even shorter the assemble method to list all the ingredients
def assemble
string = "taco with: " + ingredients.values.join(" + ")
end
And it will work as you'd expect
options1 = {:meat => "chicken", :cheese => false, :salsa => "mild"}
chicken_taco = Taco.new(options1)
puts chicken_taco.assemble() # output: taco with: chicken + false + mild
It is worth to mention that Ruby prefers chicken_tacos over chickenTacos.
Is there a way to make a method that can accept a parameter, but can also be called without one, in which case the parameter is regarded nil like the following?
some_func(variable)
some_func
def some_func(variable = nil)
...
end
Besides the more obvious option of parameters with default values, that Sawa has already shown, using arrays or hashes might be handy in some cases. Both solutions preserve nil as a an argument.
1. Receive as array:
def some_func(*args)
puts args.count
end
some_func("x", nil)
# 2
2. Send and receive as hash:
def some_func(**args)
puts args.count
end
some_func(a: "x", b: nil)
# 2
You can also use a hash as argument and have more freedom:
def print_arg(args = {})
if args.has_key?(:age)
puts args[:age]
end
end
print_arg
# =>
print_arg(age: 35, weight: 90)
# => 35
I can't find what this is called to find out more about why it works and how it works, but we've found that you can do this:
Person = Struct.new(:name)
people = [Person.new('foo'), Person.new('bar')]
# => [#<struct Person name="foo">, #<struct Person name="bar">]
people.find { |person| person.name == 'baz' }
# => nil
people.find(->{ [] }) { |person| person.name == 'baz' }
# => []
I'd like to think it's something of a fallthrough block whenever something returns nil, but perhaps someone could shed some light on how this works?
From the documentation:
find(ifnone = nil) {| obj | block } → obj or nil
find(ifnone = nil) → an_enumerator
Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns
its result when it is specified, or returns nil otherwise.
If no block is given, an enumerator is returned instead.
I have created a wildcard matcher for RR that matches JSON strings by parsing them into hashes. This is because JSON (de)serialization doesn't preserve order; if we have:
{ 'foo': 42, 'bar': 123 }
... then after (de)serialization, we might find that our update method is called with:
{ 'bar': 123, 'foo': 42 }
The wildcard matcher looks like this:
class RR::WildcardMatchers::MatchesJsonString
attr_reader :expected_json_hash
def initialize(expected_json_string)
#expected_json_hash = JSON.parse(expected_json_string)
end
def ==(other)
other.respond_to?(:expected_json_hash) && other.expected_json_hash == self.expected_json_hash
end
def wildcard_matches?(actual_json_string)
actual_json_hash = JSON.parse(actual_json_string)
#expected_json_hash == actual_json_hash
end
end
module RR::Adapters::RRMethods
def matches_json(expected_json_string)
RR::WildcardMatchers::MatchesJsonString.new(expected_json_string)
end
end
... and we're using it like:
describe 'saving manifests' do
before do
#manifests = [
{ :sections => [], 'title' => 'manifest1' },
{ :sections => [], 'title' => 'manifest2' }
]
mock(manifest).create_or_update!(matches_json(#manifests[0].to_json)) { raise 'uh oh' }
mock(manifest).create_or_update!(matches_json(#manifests[1].to_json))
parser = ContentPack::ContentPackParser.new({
'manifests' => #manifests
})
#errors = parser.save
end
it 'updates manifests' do
manifest.should have_received.create_or_update!(anything).twice
end
end
This in accordance with the RR documentation. However, instead of mock() expecting an argument that matches JSON, it expects the argument to be a MatchesJsonString object:
1) ContentPack::ContentPackParser saving manifests updates manifests
Failure/Error: mock(Manifest).create_or_update!(matches_json(#manifests[0].to_json)) { raise 'uh oh' }
RR::Errors::TimesCalledError:
create_or_update!(#<RR::WildcardMatchers::MatchesJsonString:0x13540def0 #expected_json_hash={"title"=>"manifest1", "sections"=>[]}>)
Called 0 times.
Expected 1 times.
# ./spec/models/content_pack/content_pack_parser_spec.rb:196
The answer is that there's a typo in the documentation to which I linked. This (my emphasis):
#wildcard_matches?(other)
wildcard_matches? is the method that actually checks the argument against the expectation. It should return true if other is considered to match, false otherwise. In the case of DivisibleBy, wildcard_matches? reads:
... should actually read:
#wildcard_match?(other)
...
One of my colleagues suggested that we compare our code with one of the matchers defined in the rr gem, and then the difference stood out.