globalid 0.4.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 833f7e0950a55c0aa3a0e62176de2d163a395c05
4
- data.tar.gz: 86b23bf6f2710971970c3a25a1301028184887f6
2
+ SHA256:
3
+ metadata.gz: e5a7361595eea95f395b66f4a79123ad35e61b7008d256646bea775291fa9f41
4
+ data.tar.gz: a9ece75bb440f6b49b8ad623b976bc05f4f3da2151104d045d1b7305c4a8d9b2
5
5
  SHA512:
6
- metadata.gz: 497931559ea0b7aa8d21b9ea2dfb4ae219739a8449763e2dad590c5b30f1cb4675b66841837f3f43c7480698dbbd478e4677c6ce43c01cff50a9f1e46e6af864
7
- data.tar.gz: b96a45a0e29e90e417b8282cef8bc8b6b23b079c6f25decff10ec9ad8ae5b0d08255820936460f8a58944b3a9847d4f7bb3c7ce59bf963c54319ddb6af01bc43
6
+ metadata.gz: 8290a4a79953bb92053c2f52ae847c557e7f5d4f0528124c7f1217ee4a34b5c7d42290a2584d729e0c5011c05cd1df4c9c073be49bde7d4fa13597f44a52513f
7
+ data.tar.gz: 2d9e7c573f7223406733b8be7ce1d6d764cca584bcfbb0a3f2ffb8d6f71f82970ceacf827e9c8a251975c0681b5d7d8293c1e158927dbf38f362e1337fd027ce
data/README.md CHANGED
@@ -24,17 +24,17 @@ Mix `GlobalID::Identification` into any model with a `#find(id)` class method.
24
24
  Support is automatically included in Active Record.
25
25
 
26
26
  ```ruby
27
- >> person_gid = Person.find(1).to_global_id
28
- => #<GlobalID ...
27
+ person_gid = Person.find(1).to_global_id
28
+ # => #<GlobalID ...
29
29
 
30
- >> person_gid.uri
31
- => #<URI ...
30
+ person_gid.uri
31
+ # => #<URI ...
32
32
 
33
- >> person_gid.to_s
34
- => "gid://app/Person/1"
33
+ person_gid.to_s
34
+ # => "gid://app/Person/1"
35
35
 
36
- >> GlobalID::Locator.locate person_gid
37
- => #<Person:0x007fae94bf6298 @id="1">
36
+ GlobalID::Locator.locate person_gid
37
+ # => #<Person:0x007fae94bf6298 @id="1">
38
38
  ```
39
39
 
40
40
  ### Signed Global IDs
@@ -42,79 +42,125 @@ Support is automatically included in Active Record.
42
42
  For added security GlobalIDs can also be signed to ensure that the data hasn't been tampered with.
43
43
 
44
44
  ```ruby
45
- >> person_sgid = Person.find(1).to_signed_global_id
46
- => #<SignedGlobalID:0x007fea1944b410>
45
+ person_sgid = Person.find(1).to_signed_global_id
46
+ # => #<SignedGlobalID:0x007fea1944b410>
47
47
 
48
- >> person_sgid = Person.find(1).to_sgid
49
- => #<SignedGlobalID:0x007fea1944b410>
48
+ person_sgid = Person.find(1).to_sgid
49
+ # => #<SignedGlobalID:0x007fea1944b410>
50
50
 
51
- >> person_sgid.to_s
52
- => "BAhJIh5naWQ6Ly9pZGluYWlkaS9Vc2VyLzM5NTk5BjoGRVQ=--81d7358dd5ee2ca33189bb404592df5e8d11420e"
53
-
54
- >> GlobalID::Locator.locate_signed person_sgid
55
- => #<Person:0x007fae94bf6298 @id="1">
51
+ person_sgid.to_s
52
+ # => "BAhJIh5naWQ6Ly9pZGluYWlkaS9Vc2VyLzM5NTk5BjoGRVQ=--81d7358dd5ee2ca33189bb404592df5e8d11420e"
56
53
 
54
+ GlobalID::Locator.locate_signed person_sgid
55
+ # => #<Person:0x007fae94bf6298 @id="1">
57
56
  ```
58
- You can even bump the security up some more by explaining what purpose a Signed Global ID is for.
59
- In this way evildoers can't reuse a sign-up form's SGID on the login page. For example.
60
57
 
61
- ```ruby
62
- >> signup_person_sgid = Person.find(1).to_sgid(for: 'signup_form')
63
- => #<SignedGlobalID:0x007fea1984b520
64
-
65
- >> GlobalID::Locator.locate_signed(signup_person_sgid.to_s, for: 'signup_form')
66
- => #<Person:0x007fae94bf6298 @id="1">
67
- ```
58
+ **Expiration**
68
59
 
69
- You can also have SGIDs that expire some time in the future. Useful if there's a resource,
60
+ Signed Global IDs can expire some time in the future. This is useful if there's a resource
70
61
  people shouldn't have indefinite access to, like a share link.
71
62
 
72
63
  ```ruby
73
- >> expiring_sgid = Document.find(5).to_sgid(expires_in: 2.hours, for: 'sharing')
74
- => #<SignedGlobalID:0x008fde45df8937 ...>
64
+ expiring_sgid = Document.find(5).to_sgid(expires_in: 2.hours, for: 'sharing')
65
+ # => #<SignedGlobalID:0x008fde45df8937 ...>
75
66
 
76
67
  # Within 2 hours...
77
- >> GlobalID::Locator.locate_signed(expiring_sgid.to_s, for: 'sharing')
78
- => #<Document:0x007fae94bf6298 @id="5">
68
+ GlobalID::Locator.locate_signed(expiring_sgid.to_s, for: 'sharing')
69
+ # => #<Document:0x007fae94bf6298 @id="5">
79
70
 
80
71
  # More than 2 hours later...
81
- >> GlobalID::Locator.locate_signed(expiring_sgid.to_s, for: 'sharing')
82
- => nil
72
+ GlobalID::Locator.locate_signed(expiring_sgid.to_s, for: 'sharing')
73
+ # => nil
74
+ ```
83
75
 
84
- >> explicit_expiring_sgid = SecretAgentMessage.find(5).to_sgid(expires_at: Time.now.advance(hours: 1))
85
- => #<SignedGlobalID:0x008fde45df8937 ...>
76
+ **In Rails, an auto-expiry of 1 month is set by default.** You can alter that deal
77
+ in an initializer with:
86
78
 
87
- # 1 hour later...
88
- >> GlobalID::Locator.locate_signed explicit_expiring_sgid.to_s
89
- => nil
79
+ ```ruby
80
+ # config/initializers/global_id.rb
81
+ Rails.application.config.global_id.expires_in = 3.months
82
+ ```
83
+
84
+ You can assign a default SGID lifetime like so:
90
85
 
86
+ ```ruby
87
+ SignedGlobalID.expires_in = 1.month
88
+ ```
89
+
90
+ This way any generated SGID will use that relative expiry.
91
+
92
+ It's worth noting that _expiring SGIDs are not idempotent_ because they encode the current timestamp; repeated calls to `to_sgid` will produce different results. For example, in Rails
93
+
94
+ ```ruby
95
+ Document.find(5).to_sgid.to_s == Document.find(5).to_sgid.to_s
96
+ # => false
97
+ ```
98
+
99
+ You need to explicitly pass `expires_in: nil` to generate a permanent SGID that will not expire,
100
+
101
+ ```ruby
91
102
  # Passing a false value to either expiry option turns off expiration entirely.
92
- >> never_expiring_sgid = Document.find(5).to_sgid(expires_in: nil)
93
- => #<SignedGlobalID:0x008fde45df8937 ...>
103
+ never_expiring_sgid = Document.find(5).to_sgid(expires_in: nil)
104
+ # => #<SignedGlobalID:0x008fde45df8937 ...>
94
105
 
95
106
  # Any time later...
96
- >> GlobalID::Locator.locate_signed never_expiring_sgid
97
- => #<Document:0x007fae94bf6298 @id="5">
107
+ GlobalID::Locator.locate_signed never_expiring_sgid
108
+ # => #<Document:0x007fae94bf6298 @id="5">
98
109
  ```
99
110
 
111
+ It's also possible to pass a specific expiry time
112
+
113
+ ```ruby
114
+ explicit_expiring_sgid = SecretAgentMessage.find(5).to_sgid(expires_at: Time.now.advance(hours: 1))
115
+ # => #<SignedGlobalID:0x008fde45df8937 ...>
116
+
117
+ # 1 hour later...
118
+ GlobalID::Locator.locate_signed explicit_expiring_sgid.to_s
119
+ # => nil
120
+ ```
100
121
  Note that an explicit `:expires_at` takes precedence over a relative `:expires_in`.
101
122
 
102
- You can assign a default SGID lifetime like so:
123
+ **Purpose**
124
+
125
+ You can even bump the security up some more by explaining what purpose a Signed Global ID is for.
126
+ In this way evildoers can't reuse a sign-up form's SGID on the login page. For example.
103
127
 
104
128
  ```ruby
105
- SignedGlobalID.expires_in = 1.month
129
+ signup_person_sgid = Person.find(1).to_sgid(for: 'signup_form')
130
+ # => #<SignedGlobalID:0x007fea1984b520
131
+
132
+ GlobalID::Locator.locate_signed(signup_person_sgid.to_s, for: 'signup_form')
133
+ # => #<Person:0x007fae94bf6298 @id="1">
106
134
  ```
107
135
 
108
- This way any generated SGID will use that relative expiry.
136
+ ### Locating many Global IDs
109
137
 
110
- In Rails, an auto-expiry of 1 month is set by default. You can alter that deal
111
- in an initializer with:
138
+ When needing to locate many Global IDs use `GlobalID::Locator.locate_many` or `GlobalID::Locator.locate_many_signed` for Signed Global IDs to allow loading
139
+ Global IDs more efficiently.
140
+
141
+ For instance, the default locator passes every `model_id` per `model_name` thus
142
+ using `model_name.where(id: model_ids)` versus `GlobalID::Locator.locate`'s `model_name.find(id)`.
143
+
144
+ In the case of looking up Global IDs from a database, it's only necessary to query
145
+ once per `model_name` as shown here:
112
146
 
113
147
  ```ruby
114
- # config/initializers/global_id.rb
115
- Rails.application.config.global_id.expires_in = 3.months
148
+ gids = users.concat(people).sort_by(&:id).map(&:to_global_id)
149
+ # => [#<GlobalID:0x00007ffd6a8411a0 @uri=#<URI::GID gid://app/User/1>>,
150
+ #<GlobalID:0x00007ffd675d32b8 @uri=#<URI::GID gid://app/Student/1>>,
151
+ #<GlobalID:0x00007ffd6a840b10 @uri=#<URI::GID gid://app/User/2>>,
152
+ #<GlobalID:0x00007ffd675d2c28 @uri=#<URI::GID gid://app/Student/2>>,
153
+ #<GlobalID:0x00007ffd6a840480 @uri=#<URI::GID gid://app/User/3>>,
154
+ #<GlobalID:0x00007ffd675d2598 @uri=#<URI::GID gid://app/Student/3>>]
155
+
156
+ GlobalID::Locator.locate_many gids
157
+ # SELECT "users".* FROM "users" WHERE "users"."id" IN ($1, $2, $3) [["id", 1], ["id", 2], ["id", 3]]
158
+ # SELECT "students".* FROM "students" WHERE "students"."id" IN ($1, $2, $3) [["id", 1], ["id", 2], ["id", 3]]
159
+ # => [#<User id: 1>, #<Student id: 1>, #<User id: 2>, #<Student id: 2>, #<User id: 3>, #<Student id: 3>]
116
160
  ```
117
161
 
162
+ Note the order is maintained in the returned results.
163
+
118
164
  ### Custom App Locator
119
165
 
120
166
  A custom locator can be set for an app by calling `GlobalID::Locator.use` and providing an app locator to use for that app.
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GlobalID
4
+ module FixtureSet
5
+ def signed_global_id(fixture_set_name, label, column_type: :integer, **options)
6
+ identifier = identify(label, column_type)
7
+ model_name = default_fixture_model_name(fixture_set_name)
8
+ uri = URI::GID.build([GlobalID.app, model_name, identifier, {}])
9
+
10
+ SignedGlobalID.new(uri, **options)
11
+ end
12
+ end
13
+ end
@@ -63,6 +63,11 @@ class GlobalID
63
63
  def ==(other)
64
64
  other.is_a?(GlobalID) && @uri == other.uri
65
65
  end
66
+ alias_method :eql?, :==
67
+
68
+ def hash
69
+ self.class.hash | @uri.hash
70
+ end
66
71
 
67
72
  def to_param
68
73
  # remove the = padding character for a prettier param -- it'll be added back in parse_encoded_gid
@@ -1,11 +1,7 @@
1
- require 'active_support/concern'
2
-
3
1
  class GlobalID
4
2
  module Identification
5
- extend ActiveSupport::Concern
6
-
7
3
  def to_global_id(options = {})
8
- @global_id ||= GlobalID.create(self, options)
4
+ GlobalID.create(self, options)
9
5
  end
10
6
  alias to_gid to_global_id
11
7
 
@@ -5,22 +5,26 @@ else
5
5
  require 'global_id'
6
6
  require 'active_support'
7
7
  require 'active_support/core_ext/string/inflections'
8
+ require 'active_support/core_ext/integer/time'
8
9
 
9
10
  class GlobalID
10
11
  # = GlobalID Railtie
11
12
  # Set up the signed GlobalID verifier and include Active Record support.
12
13
  class Railtie < Rails::Railtie # :nodoc:
13
14
  config.global_id = ActiveSupport::OrderedOptions.new
15
+ config.eager_load_namespaces << GlobalID
14
16
 
15
17
  initializer 'global_id' do |app|
18
+ default_expires_in = 1.month
19
+ default_app_name = app.railtie_name.remove('_application').dasherize
16
20
 
17
- app.config.global_id.app ||= app.railtie_name.remove('_application').dasherize
18
- GlobalID.app = app.config.global_id.app
19
-
20
- app.config.global_id.expires_in ||= 1.month
21
- SignedGlobalID.expires_in = app.config.global_id.expires_in
21
+ GlobalID.app = app.config.global_id.app ||= default_app_name
22
+ SignedGlobalID.expires_in = app.config.global_id.fetch(:expires_in, default_expires_in)
22
23
 
23
24
  config.after_initialize do
25
+ GlobalID.app = app.config.global_id.app ||= default_app_name
26
+ SignedGlobalID.expires_in = app.config.global_id.fetch(:expires_in, default_expires_in)
27
+
24
28
  app.config.global_id.verifier ||= begin
25
29
  GlobalID::Verifier.new(app.key_generator.generate_key('signed_global_ids'))
26
30
  rescue ArgumentError
@@ -33,6 +37,11 @@ class GlobalID
33
37
  require 'global_id/identification'
34
38
  send :include, GlobalID::Identification
35
39
  end
40
+
41
+ ActiveSupport.on_load(:active_record_fixture_set) do
42
+ require 'global_id/fixture_set'
43
+ send :extend, GlobalID::FixtureSet
44
+ end
36
45
  end
37
46
  end
38
47
  end
@@ -123,9 +123,6 @@ module URI
123
123
  private
124
124
  COMPONENT = [ :scheme, :app, :model_name, :model_id, :params ].freeze
125
125
 
126
- # Extracts model_name and model_id from the URI path.
127
- PATH_REGEXP = %r(\A/([^/]+)/?([^/]+)?\z)
128
-
129
126
  def check_host(host)
130
127
  validate_component(host)
131
128
  super
@@ -145,11 +142,11 @@ module URI
145
142
  end
146
143
 
147
144
  def set_model_components(path, validate = false)
148
- _, model_name, model_id = path.match(PATH_REGEXP).to_a
149
- model_id = CGI.unescape(model_id) if model_id
150
-
145
+ _, model_name, model_id = path.split('/', 3)
151
146
  validate_component(model_name) && validate_model_id(model_id, model_name) if validate
152
147
 
148
+ model_id = CGI.unescape(model_id) if model_id
149
+
153
150
  @model_name = model_name
154
151
  @model_id = model_id
155
152
  end
@@ -162,7 +159,7 @@ module URI
162
159
  end
163
160
 
164
161
  def validate_model_id(model_id, model_name)
165
- return model_id unless model_id.blank?
162
+ return model_id unless model_id.blank? || model_id.include?('/')
166
163
 
167
164
  raise MissingModelIdError, "Unable to create a Global ID for " \
168
165
  "#{model_name} without a model id."
@@ -173,5 +170,9 @@ module URI
173
170
  end
174
171
  end
175
172
 
176
- @@schemes['GID'] = GID
173
+ if respond_to?(:register_scheme)
174
+ register_scheme('GID', GID)
175
+ else
176
+ @@schemes['GID'] = GID
177
+ end
177
178
  end
data/lib/global_id.rb CHANGED
@@ -1,9 +1,19 @@
1
1
  require 'global_id/global_id'
2
+ require 'active_support'
2
3
 
3
4
  autoload :SignedGlobalID, 'global_id/signed_global_id'
4
5
 
5
6
  class GlobalID
6
- autoload :Locator, 'global_id/locator'
7
- autoload :Identification, 'global_id/identification'
8
- autoload :Verifier, 'global_id/verifier'
7
+ extend ActiveSupport::Autoload
8
+
9
+ eager_autoload do
10
+ autoload :Locator
11
+ autoload :Identification
12
+ autoload :Verifier
13
+ end
14
+
15
+ def self.eager_load!
16
+ super
17
+ require 'global_id/signed_global_id'
18
+ end
9
19
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: globalid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-16 00:00:00.000000000 Z
11
+ date: 2023-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.2.0
19
+ version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.2.0
26
+ version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -47,6 +47,7 @@ files:
47
47
  - MIT-LICENSE
48
48
  - README.md
49
49
  - lib/global_id.rb
50
+ - lib/global_id/fixture_set.rb
50
51
  - lib/global_id/global_id.rb
51
52
  - lib/global_id/identification.rb
52
53
  - lib/global_id/locator.rb
@@ -59,7 +60,7 @@ homepage: http://www.rubyonrails.org
59
60
  licenses:
60
61
  - MIT
61
62
  metadata: {}
62
- post_install_message:
63
+ post_install_message:
63
64
  rdoc_options: []
64
65
  require_paths:
65
66
  - lib
@@ -67,16 +68,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
67
68
  requirements:
68
69
  - - ">="
69
70
  - !ruby/object:Gem::Version
70
- version: 1.9.3
71
+ version: 2.5.0
71
72
  required_rubygems_version: !ruby/object:Gem::Requirement
72
73
  requirements:
73
74
  - - ">="
74
75
  - !ruby/object:Gem::Version
75
76
  version: '0'
76
77
  requirements: []
77
- rubyforge_project:
78
- rubygems_version: 2.6.11
79
- signing_key:
78
+ rubygems_version: 3.5.0.dev
79
+ signing_key:
80
80
  specification_version: 4
81
81
  summary: 'Refer to any model with a URI: gid://app/class/id'
82
82
  test_files: []