Overrides `#eql?` (hash equality) and `#hash` so that Stripe objects can
be used more easily as Hash keys and that certain other frameworks that
rely on these methods will have an easier time (e.g. RSpec's `change`,
see #687).
I think this might be a little controversial if we weren't already
overriding the `#==` implementation, but because we are, I think it
makes sense to extent it to these two methods as well.
Adds the magic `frozen_string_literal: true` comment to every file and
enables a Rubocop rule to make sure that it's always going to be there
going forward as well.
See here for more background [1], but the basic idea is that unlike many
other languages, static strings in code are mutable by default. This has
since been acknowledged as not a particularly good idea, and the
intention is to rectify the mistake when Ruby 3 comes out, where all
string literals will be frozen. The `frozen_string_literal` magic
comment was introduced in Ruby 2.3 as a way of easing the transition,
and allows libraries and projects to freeze their literals in advance.
I don't think this is breaking in any way: it's possible that users
might've been pulling out one of are literals somehow and mutating it,
but that would probably not have been useful for anything and would
certainly not be recommended, so I'm quite comfortable pushing this
change through as a minor version.
As discussed in #641.
[1] https://stackoverflow.com/a/37799399
The test suite is currently throwing a bunch of warnings from some
recent changes I made -- although we initialize `@additive_params` when
setting one with `self.additive_object_param`, we don't when we check
one with `self.additive_object_param?`. This often isn't a problem
because every API resource sets `metadata`, but it is from the test
suite and probably for vanilla `StripeObject`s too.
So we have a bit of a problem right now when it comes to replacing a
`StripeObject` that's embedded in an API resource.
Most of the time when someone does this, they want to _replace_ an
object embedded in another object. Take setting a source on a
subscription for example:
``` ruby
subscription.source = {
object: 'card',
number: 123,
}
subscription.save
```
In the case above, the serialized parameters should come out as:
```
source[object]=card&source[number]=123
```
That should apply even if the previous source had something else set on
it which we're not going to set this time -- say an optional parameter
like `source[address_state]`. Those should not be present at all in the
final serialized parameters.
(Another example is setting a `payout_schedule` as seen in #631 which is
PR is intended to address.)
There is an exception to this rule in the form of metadata though.
Metadata is a bit of a strange case in that the API will treat it as
additive, so if we send `metadata[foo]`, that will set the `foo` key,
but it won't overwrite any other keys that were already present.
This is a problem because when a user fully sets `metadata` to a new
object in Ruby, what they're probably trying to do is _replace_ it
rather than add to it. For example:
``` ruby
subscription.metadata
=> { old: 'bar' }
subscription.metadata = {
new: 'baz'
}
subscription.save
```
To accomplish what the user is probably trying to do, we actually need
to send `metadata[old]=&metadata[new]=baz` so that we empty the value of
`old` while simultaneously setting `new` to `baz`.
In summary, metadata behaves different from other embedded objects in a
fairly fundamental way, and because the code is currently only set up to
handle the metadata case, it's not behaving correctly when other types
of objects are being set. A lot of the time emptying values like we do
for `metadata` is benign, but as we've seen in #631, sometimes it's not.
In this patch, I modify serialization to only empty out object values
when we see that parameter is `metadata`.
I'm really not crazy about the implementation here _at all_, but I'm
having trouble thinking of a better way to do it. One possibility is to
introduce a new class annotation like `empty_embedded_object :metadata`,
but that will have to go everywhere and might be error-prone in case
someone forgets it on a new resource type. If anyone has a suggestion
for an alternative (or can let me know if I'm missing something), I'd
love to hear it.
This PR is an alternate to #631.
Backtracks a little bit #586 by bringing back custom `StripeObject`
encoding and decoding methods for Ruby's `Marshal`. These work by just
persisting values and some opts, and skipping everything else. It's
mostly the same as what we had before, but implemented a little more
cleanly so that we don't actually need to invoke `Marshal` anywhere
ourselves.
In #586 we still managed to remove all the uses of `Marshal` in our own
codebase to make the linter happy. Even though we wouldn't recommend the
use of `Marshal`, this code at least enables it for anyone using a Rails
cache or similar mechanism.
Addresses #90.
We were previously using a bit of a hack to get a free deep copy
implementation through Ruby's marshaling framework. Lint call this out
as a security problem though, and rightfully so: when combined with
unsanitized user input, unmarshaling can result in very serious security
breaches involving arbitrary code execution.
This patch removes all uses of marshal/unmarshal in favor of
implementing a deep copy method for `StripeObject`. I also reworked some
of the constants around what keys are available for `opts`. I'm still
not completely happy with the results, but I think it's going to need a
slightly larger refactor in order to get somewhere truly good.
There is what could be a breaking change for people doing non-standard
stuff with the library: the opts that we copy with an object are now
whitelisted, so if they were being used to pass around extraneous data,
that might not work as expected anymore. But because this is a contract
that we never committed to, I don't think I'd bump the major version for
change.
Removes Rubocop TODO around guard clauses and fixes the outstanding
offenses.
This is starting to get into territory that feels of more dubious value
to me, but at least it did get me writing a couple more tests, so let's
see how it goes by keeping this on.
I wanted to see what fixing Rubocop TODOs was like, so I tried to
eliminate all the easy ones. Most of these were pretty easy, and the
changes required are relatively minimal.
Some of the stuff left is harder. Pretty much everything under
`Metrics/*` is going to be a pretty big yak shave. A few of the others
are just going to need a little more work (e.g. `Style/ClassVars` and
`Style/GuardClause`). Going to stop here for now.
Tweaks the serialization behavior so that when a resource is explicitly
set to a resource's field and that resource is subsequently saved, then
if it looks like the set resource was persisted we extract its ID and
send it up to the API.
By slight extension we also throw an `ArgumentError` if it looks like
that set resource was _not_ persisted because if the user set it
explicitly then it was probably not their intention to have it silently
ignored by the library in the event of a problem.
Previously, the value of whatever accessor was missing was left out of
the call to build it. This had the effect of skipping over the
lazily-built predicate method when the missing accessor is for a
boolean.
Of course, I realize this all is a bit contrived as most of the time
folks aren't assigning these values manually to stripe objects. However,
in my testing it caught me by surprised that the behavior is asymmetric
depending on how and when values are assigned.
As such I believe this is a less surprising implementation.
As described in #481, adding a protected field like `legal_entity` as
part of an update API operation can cause some issues like a custom
encoding scheme not being considered and special handling around empty
values being ignored.
As a an easy fix for this, let's disallow access to protected fields in
the same way that we disallow them from being set directly on an
instance of a given model.
Helps address (but is not a complete fix for) #481.
Introduce a `#save_with_parent` flag that allows the default behavior of
never saving API resources nested under a parent to be overridden, a
feature that we so far only know to need for updating a source under a
customer.
I'm not sure exactly what changed here (did we change the `$VERBOSE`
setting?), but I'm not seeing a whole lot of warnings when running the
test suites locally and in CI. For example:
```
Started
........................................./home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
............../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
......../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
.../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
........./home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
...
..../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
....../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
..../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
......./home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
........./home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
........../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
................./home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
.../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
..../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
....../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
..........
........./home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
....../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
......../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
......../home/travis/build/stripe/stripe-ruby/lib/stripe/api_operations/list.rb:6: warning: instance variable @opts not initialized
............./home/travis/build/stripe/stripe-ruby/lib/stripe/stripe_object.rb:35: warning: instance variable @values not initialized
./home/travis/build/stripe/stripe-ruby/lib/stripe/stripe_object.rb:35: warning: instance variable @values not initialized
...................../home/travis/build/stripe/stripe-ruby/lib/stripe/transfer.rb:8: warning: instance variable @api_key not initialized
..............
..
Finished in 0.785621037 seconds.
```
Most of these are due to unused or uninitialized variables. This patch
fixes all warnings by fixing offending code.
Prior to my last major serialization refactor, there was a check in the
code that would remove any subobjects from serialization that were of
their own proper resource type (for example, if a charge contained a
customer, that customer would be removed).
What I didn't realize at the time is that the old serialization code had
a bug/quirk that would allow *certain types* of subobjects that were API
resources to make it through unscathed.
In short, the behavior requirement here is *directly* contradictory.
There was a test in place that would make sure that `customer` was
removed from this hash:
``` ruby
{
:id => 'ch_id',
:object => 'charge',
:customer => {
:object => 'customer',
:id => 'customer_id'
}
}
```
But, as reported in #406, we expect, and indeed need, for `source` (a
card) to make it through to the API in this hash:
``` ruby
{
:id => 'cus_id',
:object => 'customer',
:source => {
:object => 'card',
:id => 'card_id'
}
}
```
My proposal here is to just remove the check on serializing API
resources. The normal code that only sends up keys/hashes that have
changed is still in place, so in the first example, `customer` still
isn't sent unless the user has directly manipulated a field on that
subobject. I propose that in those cases we allow the API itself to
reject the request rather than try to cut it off at the client level.
Unfortunately, there is some possibility that removing those API
resources is important for some reason, but of course there's no
documentation on it beyond the after-the-fact post-justification that I
wrote during my last refactor. I can't think of any reason that it would
be too destructive, but there is some level of risk.
This pull does two major things:
1. Refactors `serialize_params` to be more concise and readable while
still complying to our existing test suite. Unfortunately over time
this method has become a ball of mud that's very difficult to reason
about, as recently evidenced by #384.
2. Moves `serialize_params` from class method to instance method (while
still keeping for old class method for backwards compatibility). This
is to give it a more sane interface.
Hashes are converted to StripeObject when used as params of save.
They need to be converted to hash on serialize.
Signed-off-by: François de Metz <francois@stormz.me>
Signed-off-by: Cyril Duez <cyril@stormz.me>
This is kind of a weird one because it'll only cause a failure when
serializing a subobject or hash of a `StripeObject`, but it's good
practice to initialize instance variables anyway.
Fixes#360.
We attempt to do a special encoding trick when serializing
`additional_owners` under an account: when updating a value, we actually
send the update parameters up as an integer-indexed hash rather than an
array. So instead of this:
field[]=item1&field[]=item2&field[]=item3
We send this:
field[0]=item1&field[1]=item2&field[2]=item3
The trouble is that this had previously been built into the core library
as the default handling for all arrays. Because of this, it was
impossible to resize a non-`additional_owners` array as described in
more detail in #340.
This patch special cases `additional_owners` and brings sane behavior
back to normal arrays along with a test suite so that we try to build
some better guarantees around both the general and non-general cases.
Unfortunately usage of `#update_attributes` had rolled over from a time
where `#update_attributes_with_options` was still in use and `opts` were
being passed in as an optional argument which had the result of further
nesting the hash internally (i.e. `:opts => { :opts => ... } }`).
This patch fixes that problem, adds a regression test to prevent it from
reappearing, and banishes the unused `#update_attributes_with_options`.
Fixes#334.
Adds a special helper to `StripeObject` that helps a developer to
determine whether or not an object is deleted.
As described originally in #257, this is a bit of a special case because
a non-deleted object does not respond with `deleted` as part of its
representation, so while a deleted object would have this accessor
available automatically, non-deleted objects would not. This made use of
the SDK awkward because the presence of the method was not guaranteed.
Fixes#257 (again, heh).