google-cloud-bigquery 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/google-cloud-bigquery.rb +25 -0
- data/lib/google/cloud/bigquery.rb +61 -11
- data/lib/google/cloud/bigquery/convert.rb +1 -1
- data/lib/google/cloud/bigquery/credentials.rb +6 -6
- data/lib/google/cloud/bigquery/data.rb +6 -6
- data/lib/google/cloud/bigquery/dataset.rb +16 -15
- data/lib/google/cloud/bigquery/dataset/access.rb +38 -30
- data/lib/google/cloud/bigquery/dataset/list.rb +1 -1
- data/lib/google/cloud/bigquery/external.rb +22 -20
- data/lib/google/cloud/bigquery/insert_response.rb +0 -2
- data/lib/google/cloud/bigquery/job.rb +39 -31
- data/lib/google/cloud/bigquery/job/list.rb +1 -1
- data/lib/google/cloud/bigquery/load_job.rb +4 -4
- data/lib/google/cloud/bigquery/project.rb +7 -15
- data/lib/google/cloud/bigquery/project/list.rb +1 -1
- data/lib/google/cloud/bigquery/query_job.rb +12 -12
- data/lib/google/cloud/bigquery/schema.rb +7 -7
- data/lib/google/cloud/bigquery/schema/field.rb +12 -12
- data/lib/google/cloud/bigquery/service.rb +44 -29
- data/lib/google/cloud/bigquery/table.rb +78 -21
- data/lib/google/cloud/bigquery/table/async_inserter.rb +42 -17
- data/lib/google/cloud/bigquery/table/list.rb +1 -1
- data/lib/google/cloud/bigquery/version.rb +1 -1
- metadata +11 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb7f8896ef2f04b07d335a2d619a60557246633ef343255c7fd6f2c31c1afbcf
|
4
|
+
data.tar.gz: 8445e2df96afbd0be615ee17d891d3b192a2a6a32f21f010113809c020e6aa76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ead4df8de6a2db97edf826bfdf82c49b48e7eebd999d3ee1448616a5b1d53d219bcc66dfe13fbe6e005282d7a59582ff4ead46adbe10a25cb365b19ebb0a5ba2
|
7
|
+
data.tar.gz: f1de83104a82b84673b071395aa1527bc4ed3d992c2ed0b7b04b9cbb13f3f303277da04ee93e944411ae0cab10cdea573783da6ef9ece20782f429c34da76ac2
|
@@ -20,6 +20,8 @@
|
|
20
20
|
|
21
21
|
gem "google-cloud-core"
|
22
22
|
require "google/cloud"
|
23
|
+
require "google/cloud/config"
|
24
|
+
require "googleauth"
|
23
25
|
|
24
26
|
module Google
|
25
27
|
module Cloud
|
@@ -111,3 +113,26 @@ module Google
|
|
111
113
|
end
|
112
114
|
end
|
113
115
|
end
|
116
|
+
|
117
|
+
# Set the default bigquery configuration
|
118
|
+
Google::Cloud.configure.add_config! :bigquery do |config|
|
119
|
+
default_project = Google::Cloud::Config.deferred do
|
120
|
+
ENV["BIGQUERY_PROJECT"]
|
121
|
+
end
|
122
|
+
default_creds = Google::Cloud::Config.deferred do
|
123
|
+
Google::Cloud::Config.credentials_from_env(
|
124
|
+
"BIGQUERY_CREDENTIALS", "BIGQUERY_CREDENTIALS_JSON",
|
125
|
+
"BIGQUERY_KEYFILE", "BIGQUERY_KEYFILE_JSON"
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
config.add_field! :project_id, default_project, match: String, allow_nil: true
|
130
|
+
config.add_alias! :project, :project_id
|
131
|
+
config.add_field! :credentials, default_creds,
|
132
|
+
match: [String, Hash, Google::Auth::Credentials],
|
133
|
+
allow_nil: true
|
134
|
+
config.add_alias! :keyfile, :credentials
|
135
|
+
config.add_field! :scope, nil, match: [String, Array]
|
136
|
+
config.add_field! :retries, nil, match: Integer
|
137
|
+
config.add_field! :timeout, nil, match: Integer
|
138
|
+
end
|
@@ -15,6 +15,8 @@
|
|
15
15
|
|
16
16
|
require "google-cloud-bigquery"
|
17
17
|
require "google/cloud/bigquery/project"
|
18
|
+
require "google/cloud/config"
|
19
|
+
require "google/cloud/env"
|
18
20
|
|
19
21
|
module Google
|
20
22
|
module Cloud
|
@@ -46,13 +48,13 @@ module Google
|
|
46
48
|
#
|
47
49
|
# A BigQuery project contains datasets, which in turn contain tables.
|
48
50
|
# Assuming that you have not yet created datasets or tables in your own
|
49
|
-
# project, let's connect to Google's `
|
50
|
-
# find.
|
51
|
+
# project, let's connect to Google's `bigquery-public-data` project, and see
|
52
|
+
# what we find.
|
51
53
|
#
|
52
54
|
# ```ruby
|
53
55
|
# require "google/cloud/bigquery"
|
54
56
|
#
|
55
|
-
# bigquery = Google::Cloud::Bigquery.new project: "
|
57
|
+
# bigquery = Google::Cloud::Bigquery.new project: "bigquery-public-data"
|
56
58
|
#
|
57
59
|
# bigquery.datasets.count #=> 1
|
58
60
|
# bigquery.datasets.first.dataset_id #=> "samples"
|
@@ -72,7 +74,7 @@ module Google
|
|
72
74
|
# ```ruby
|
73
75
|
# require "google/cloud/bigquery"
|
74
76
|
#
|
75
|
-
# bigquery = Google::Cloud::Bigquery.new project: "
|
77
|
+
# bigquery = Google::Cloud::Bigquery.new project: "bigquery-public-data"
|
76
78
|
#
|
77
79
|
# dataset = bigquery.dataset "samples"
|
78
80
|
# table = dataset.table "shakespeare"
|
@@ -145,7 +147,7 @@ module Google
|
|
145
147
|
# bigquery = Google::Cloud::Bigquery.new
|
146
148
|
#
|
147
149
|
# sql = "SELECT TOP(word, 50) as word, COUNT(*) as count " \
|
148
|
-
# "FROM [
|
150
|
+
# "FROM [bigquery-public-data:samples.shakespeare]"
|
149
151
|
# data = bigquery.query sql, legacy_sql: true
|
150
152
|
# ```
|
151
153
|
#
|
@@ -210,7 +212,7 @@ module Google
|
|
210
212
|
#
|
211
213
|
# sql = "SELECT APPROX_TOP_COUNT(corpus, 10) as title, " \
|
212
214
|
# "COUNT(*) as unique_words " \
|
213
|
-
# "FROM
|
215
|
+
# "FROM `bigquery-public-data.samples.shakespeare`"
|
214
216
|
# data = bigquery.query sql
|
215
217
|
#
|
216
218
|
# data.next? #=> false
|
@@ -237,7 +239,7 @@ module Google
|
|
237
239
|
#
|
238
240
|
# sql = "SELECT APPROX_TOP_COUNT(corpus, 10) as title, " \
|
239
241
|
# "COUNT(*) as unique_words " \
|
240
|
-
# "FROM
|
242
|
+
# "FROM `bigquery-public-data.samples.shakespeare`"
|
241
243
|
# job = bigquery.query_job sql
|
242
244
|
#
|
243
245
|
# job.wait_until_done!
|
@@ -521,18 +523,66 @@ module Google
|
|
521
523
|
#
|
522
524
|
def self.new project_id: nil, credentials: nil, scope: nil, retries: nil,
|
523
525
|
timeout: nil, project: nil, keyfile: nil
|
524
|
-
project_id ||= (project ||
|
526
|
+
project_id ||= (project || default_project_id)
|
525
527
|
project_id = project_id.to_s # Always cast to a string
|
526
|
-
|
528
|
+
raise ArgumentError, "project_id is missing" if project_id.empty?
|
527
529
|
|
528
|
-
|
530
|
+
scope ||= configure.scope
|
531
|
+
retries ||= configure.retries
|
532
|
+
timeout ||= configure.timeout
|
533
|
+
|
534
|
+
credentials ||= (keyfile || default_credentials(scope: scope))
|
529
535
|
unless credentials.is_a? Google::Auth::Credentials
|
530
536
|
credentials = Bigquery::Credentials.new credentials, scope: scope
|
531
537
|
end
|
532
538
|
|
533
539
|
Bigquery::Project.new(
|
534
540
|
Bigquery::Service.new(
|
535
|
-
project_id, credentials, retries: retries, timeout: timeout
|
541
|
+
project_id, credentials, retries: retries, timeout: timeout
|
542
|
+
)
|
543
|
+
)
|
544
|
+
end
|
545
|
+
|
546
|
+
##
|
547
|
+
# Configure the Google Cloud BigQuery library.
|
548
|
+
#
|
549
|
+
# The following BigQuery configuration parameters are supported:
|
550
|
+
#
|
551
|
+
# * `project_id` - (String) Identifier for a BigQuery project. (The
|
552
|
+
# parameter `project` is considered deprecated, but may also be used.)
|
553
|
+
# * `credentials` - (String, Hash, Google::Auth::Credentials) The path to
|
554
|
+
# the keyfile as a String, the contents of the keyfile as a Hash, or a
|
555
|
+
# Google::Auth::Credentials object. (See {Bigquery::Credentials}) (The
|
556
|
+
# parameter `keyfile` is considered deprecated, but may also be used.)
|
557
|
+
# * `scope` - (String, Array<String>) The OAuth 2.0 scopes controlling
|
558
|
+
# the set of resources and operations that the connection can access.
|
559
|
+
# * `retries` - (Integer) Number of times to retry requests on server
|
560
|
+
# error.
|
561
|
+
# * `timeout` - (Integer) Default timeout to use in requests.
|
562
|
+
#
|
563
|
+
# @return [Google::Cloud::Config] The configuration object the
|
564
|
+
# Google::Cloud::Bigquery library uses.
|
565
|
+
#
|
566
|
+
def self.configure
|
567
|
+
yield Google::Cloud.configure.bigquery if block_given?
|
568
|
+
|
569
|
+
Google::Cloud.configure.bigquery
|
570
|
+
end
|
571
|
+
|
572
|
+
##
|
573
|
+
# @private Default project.
|
574
|
+
def self.default_project_id
|
575
|
+
Google::Cloud.configure.bigquery.project_id ||
|
576
|
+
Google::Cloud.configure.project_id ||
|
577
|
+
Google::Cloud.env.project_id
|
578
|
+
end
|
579
|
+
|
580
|
+
##
|
581
|
+
# @private Default credentials.
|
582
|
+
def self.default_credentials scope: nil
|
583
|
+
Google::Cloud.configure.bigquery.credentials ||
|
584
|
+
Google::Cloud.configure.credentials ||
|
585
|
+
Bigquery::Credentials.default(scope: scope)
|
536
586
|
end
|
537
587
|
end
|
538
588
|
end
|
@@ -38,19 +38,19 @@ module Google
|
|
38
38
|
# bigquery.project_id #=> "my-project"
|
39
39
|
#
|
40
40
|
class Credentials < Google::Auth::Credentials
|
41
|
-
SCOPE = ["https://www.googleapis.com/auth/bigquery"]
|
42
|
-
PATH_ENV_VARS = %w
|
41
|
+
SCOPE = ["https://www.googleapis.com/auth/bigquery"].freeze
|
42
|
+
PATH_ENV_VARS = %w[BIGQUERY_CREDENTIALS
|
43
43
|
GOOGLE_CLOUD_CREDENTIALS
|
44
44
|
BIGQUERY_KEYFILE
|
45
45
|
GOOGLE_CLOUD_KEYFILE
|
46
|
-
GCLOUD_KEYFILE
|
47
|
-
JSON_ENV_VARS = %w
|
46
|
+
GCLOUD_KEYFILE].freeze
|
47
|
+
JSON_ENV_VARS = %w[BIGQUERY_CREDENTIALS_JSON
|
48
48
|
GOOGLE_CLOUD_CREDENTIALS_JSON
|
49
49
|
BIGQUERY_KEYFILE_JSON
|
50
50
|
GOOGLE_CLOUD_KEYFILE_JSON
|
51
|
-
GCLOUD_KEYFILE_JSON
|
51
|
+
GCLOUD_KEYFILE_JSON].freeze
|
52
52
|
DEFAULT_PATHS = \
|
53
|
-
["~/.config/gcloud/application_default_credentials.json"]
|
53
|
+
["~/.config/gcloud/application_default_credentials.json"].freeze
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -33,7 +33,7 @@ module Google
|
|
33
33
|
#
|
34
34
|
# bigquery = Google::Cloud::Bigquery.new
|
35
35
|
#
|
36
|
-
# sql = "SELECT word FROM
|
36
|
+
# sql = "SELECT word FROM `bigquery-public-data.samples.shakespeare`"
|
37
37
|
# job = bigquery.query_job sql
|
38
38
|
#
|
39
39
|
# job.wait_until_done!
|
@@ -105,7 +105,7 @@ module Google
|
|
105
105
|
#
|
106
106
|
# bigquery = Google::Cloud::Bigquery.new
|
107
107
|
#
|
108
|
-
# sql = "SELECT word FROM
|
108
|
+
# sql = "SELECT word FROM `bigquery-public-data.samples.shakespeare`"
|
109
109
|
# job = bigquery.query_job sql
|
110
110
|
#
|
111
111
|
# job.wait_until_done!
|
@@ -120,7 +120,7 @@ module Google
|
|
120
120
|
#
|
121
121
|
def total
|
122
122
|
Integer @gapi_json[:totalRows]
|
123
|
-
rescue
|
123
|
+
rescue StandardError
|
124
124
|
nil
|
125
125
|
end
|
126
126
|
|
@@ -205,7 +205,7 @@ module Google
|
|
205
205
|
#
|
206
206
|
# bigquery = Google::Cloud::Bigquery.new
|
207
207
|
#
|
208
|
-
# sql = "SELECT word FROM
|
208
|
+
# sql = "SELECT word FROM `bigquery-public-data.samples.shakespeare`"
|
209
209
|
# job = bigquery.query_job sql
|
210
210
|
#
|
211
211
|
# job.wait_until_done!
|
@@ -232,7 +232,7 @@ module Google
|
|
232
232
|
#
|
233
233
|
# bigquery = Google::Cloud::Bigquery.new
|
234
234
|
#
|
235
|
-
# sql = "SELECT word FROM
|
235
|
+
# sql = "SELECT word FROM `bigquery-public-data.samples.shakespeare`"
|
236
236
|
# job = bigquery.query_job sql
|
237
237
|
#
|
238
238
|
# job.wait_until_done!
|
@@ -343,7 +343,7 @@ module Google
|
|
343
343
|
##
|
344
344
|
# Raise an error unless an active service is available.
|
345
345
|
def ensure_service!
|
346
|
-
|
346
|
+
raise "Must have active connection" unless service
|
347
347
|
end
|
348
348
|
end
|
349
349
|
end
|
@@ -202,7 +202,7 @@ module Google
|
|
202
202
|
ensure_full_data!
|
203
203
|
begin
|
204
204
|
Integer @gapi.default_table_expiration_ms
|
205
|
-
rescue
|
205
|
+
rescue StandardError
|
206
206
|
nil
|
207
207
|
end
|
208
208
|
end
|
@@ -239,7 +239,7 @@ module Google
|
|
239
239
|
ensure_full_data!
|
240
240
|
begin
|
241
241
|
::Time.at(Integer(@gapi.creation_time) / 1000.0)
|
242
|
-
rescue
|
242
|
+
rescue StandardError
|
243
243
|
nil
|
244
244
|
end
|
245
245
|
end
|
@@ -257,7 +257,7 @@ module Google
|
|
257
257
|
ensure_full_data!
|
258
258
|
begin
|
259
259
|
::Time.at(Integer(@gapi.last_modified_time) / 1000.0)
|
260
|
-
rescue
|
260
|
+
rescue StandardError
|
261
261
|
nil
|
262
262
|
end
|
263
263
|
end
|
@@ -494,7 +494,9 @@ module Google
|
|
494
494
|
new_tb = Google::Apis::BigqueryV2::Table.new(
|
495
495
|
table_reference: Google::Apis::BigqueryV2::TableReference.new(
|
496
496
|
project_id: project_id, dataset_id: dataset_id,
|
497
|
-
table_id: table_id
|
497
|
+
table_id: table_id
|
498
|
+
)
|
499
|
+
)
|
498
500
|
updater = Table::Updater.new(new_tb).tap do |tb|
|
499
501
|
tb.name = name unless name.nil?
|
500
502
|
tb.description = description unless description.nil?
|
@@ -1070,8 +1072,8 @@ module Google
|
|
1070
1072
|
if job.failed?
|
1071
1073
|
begin
|
1072
1074
|
# raise to activate ruby exception cause handling
|
1073
|
-
|
1074
|
-
rescue => e
|
1075
|
+
raise job.gapi_error
|
1076
|
+
rescue StandardError => e
|
1075
1077
|
# wrap Google::Apis::Error with Google::Cloud::Error
|
1076
1078
|
raise Google::Cloud::Error.from_error(e)
|
1077
1079
|
end
|
@@ -1363,7 +1365,7 @@ module Google
|
|
1363
1365
|
null_marker: null_marker }
|
1364
1366
|
return load_storage(table_id, file, options) if storage_url? file
|
1365
1367
|
return load_local(table_id, file, options) if local_file? file
|
1366
|
-
|
1368
|
+
raise Google::Cloud::Error, "Don't know how to load #{file}"
|
1367
1369
|
end
|
1368
1370
|
|
1369
1371
|
##
|
@@ -1570,8 +1572,8 @@ module Google
|
|
1570
1572
|
if job.failed?
|
1571
1573
|
begin
|
1572
1574
|
# raise to activate ruby exception cause handling
|
1573
|
-
|
1574
|
-
rescue => e
|
1575
|
+
raise job.gapi_error
|
1576
|
+
rescue StandardError => e
|
1575
1577
|
# wrap Google::Apis::Error with Google::Cloud::Error
|
1576
1578
|
raise Google::Cloud::Error.from_error(e)
|
1577
1579
|
end
|
@@ -1601,7 +1603,7 @@ module Google
|
|
1601
1603
|
@gapi = reloaded_gapi
|
1602
1604
|
self
|
1603
1605
|
end
|
1604
|
-
|
1606
|
+
alias refresh! reload!
|
1605
1607
|
|
1606
1608
|
##
|
1607
1609
|
# Determines whether the dataset exists in the BigQuery service. The
|
@@ -1867,8 +1869,7 @@ module Google
|
|
1867
1869
|
#
|
1868
1870
|
# bigquery = Google::Cloud::Bigquery.new
|
1869
1871
|
# dataset = bigquery.dataset "my_dataset"
|
1870
|
-
#
|
1871
|
-
# inserter = table.insert_async do |result|
|
1872
|
+
# inserter = dataset.insert_async "my_table" do |result|
|
1872
1873
|
# if result.error?
|
1873
1874
|
# log_error result.error
|
1874
1875
|
# else
|
@@ -1904,7 +1905,7 @@ module Google
|
|
1904
1905
|
|
1905
1906
|
def insert_data table_id, rows, skip_invalid: nil, ignore_unknown: nil
|
1906
1907
|
rows = [rows] if rows.is_a? Hash
|
1907
|
-
|
1908
|
+
raise ArgumentError, "No rows provided" if rows.empty?
|
1908
1909
|
ensure_service!
|
1909
1910
|
options = { skip_invalid: skip_invalid,
|
1910
1911
|
ignore_unknown: ignore_unknown }
|
@@ -1915,7 +1916,7 @@ module Google
|
|
1915
1916
|
##
|
1916
1917
|
# Raise an error unless an active service is available.
|
1917
1918
|
def ensure_service!
|
1918
|
-
|
1919
|
+
raise "Must have active connection" unless service
|
1919
1920
|
end
|
1920
1921
|
|
1921
1922
|
##
|
@@ -1972,7 +1973,7 @@ module Google
|
|
1972
1973
|
|
1973
1974
|
def local_file? file
|
1974
1975
|
::File.file? file
|
1975
|
-
rescue
|
1976
|
+
rescue StandardError
|
1976
1977
|
false
|
1977
1978
|
end
|
1978
1979
|
|
@@ -40,36 +40,42 @@ module Google
|
|
40
40
|
#
|
41
41
|
class Access
|
42
42
|
# @private
|
43
|
-
ROLES = {
|
44
|
-
|
45
|
-
|
43
|
+
ROLES = {
|
44
|
+
"reader" => "READER",
|
45
|
+
"writer" => "WRITER",
|
46
|
+
"owner" => "OWNER"
|
47
|
+
}.freeze
|
46
48
|
|
47
49
|
# @private
|
48
|
-
SCOPES = {
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
50
|
+
SCOPES = {
|
51
|
+
"user" => :user_by_email,
|
52
|
+
"user_by_email" => :user_by_email,
|
53
|
+
"userByEmail" => :user_by_email,
|
54
|
+
"group" => :group_by_email,
|
55
|
+
"group_by_email" => :group_by_email,
|
56
|
+
"groupByEmail" => :group_by_email,
|
57
|
+
"domain" => :domain,
|
58
|
+
"special" => :special_group,
|
59
|
+
"special_group" => :special_group,
|
60
|
+
"specialGroup" => :special_group,
|
61
|
+
"view" => :view
|
62
|
+
}.freeze
|
59
63
|
|
60
64
|
# @private
|
61
|
-
GROUPS = {
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
65
|
+
GROUPS = {
|
66
|
+
"owners" => "projectOwners",
|
67
|
+
"project_owners" => "projectOwners",
|
68
|
+
"projectOwners" => "projectOwners",
|
69
|
+
"readers" => "projectReaders",
|
70
|
+
"project_readers" => "projectReaders",
|
71
|
+
"projectReaders" => "projectReaders",
|
72
|
+
"writers" => "projectWriters",
|
73
|
+
"project_writers" => "projectWriters",
|
74
|
+
"projectWriters" => "projectWriters",
|
75
|
+
"all" => "allAuthenticatedUsers",
|
76
|
+
"all_authenticated_users" => "allAuthenticatedUsers",
|
77
|
+
"allAuthenticatedUsers" => "allAuthenticatedUsers"
|
78
|
+
}.freeze
|
73
79
|
|
74
80
|
##
|
75
81
|
# @private
|
@@ -891,7 +897,7 @@ module Google
|
|
891
897
|
def validate_role role
|
892
898
|
good_role = ROLES[role.to_s]
|
893
899
|
if good_role.nil?
|
894
|
-
|
900
|
+
raise ArgumentError "Unable to determine role for #{role}"
|
895
901
|
end
|
896
902
|
good_role
|
897
903
|
end
|
@@ -900,7 +906,7 @@ module Google
|
|
900
906
|
def validate_scope scope
|
901
907
|
good_scope = SCOPES[scope.to_s]
|
902
908
|
if good_scope.nil?
|
903
|
-
|
909
|
+
raise ArgumentError "Unable to determine scope for #{scope}"
|
904
910
|
end
|
905
911
|
good_scope
|
906
912
|
end
|
@@ -953,7 +959,8 @@ module Google
|
|
953
959
|
value = validate_special_group(value) if scope == :special_group
|
954
960
|
# Remove any rules of this role, scope, and value
|
955
961
|
@rules.reject!(
|
956
|
-
&find_by_role_and_scope_and_value(role, scope, value)
|
962
|
+
&find_by_role_and_scope_and_value(role, scope, value)
|
963
|
+
)
|
957
964
|
end
|
958
965
|
|
959
966
|
# @private
|
@@ -972,7 +979,8 @@ module Google
|
|
972
979
|
value = validate_special_group(value) if scope == :special_group
|
973
980
|
# Detect any rules of this role, scope, and value
|
974
981
|
!(!@rules.detect(
|
975
|
-
&find_by_role_and_scope_and_value(role, scope, value)
|
982
|
+
&find_by_role_and_scope_and_value(role, scope, value)
|
983
|
+
))
|
976
984
|
end
|
977
985
|
|
978
986
|
# @private
|