google-cloud-bigquery 1.24.0 → 1.29.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32f4b672ccecbbd45bfb9790fc4110a22244a6459c459aa40bb1c1b4b77c1094
4
- data.tar.gz: 2ac69379fefad5547aac47be509a50945af647d7fe7b61e1653fc4dd9c9ca865
3
+ metadata.gz: 46f8452c3539550b91ccbde1577ca5e9d7f0d7ad39976a92103590f919d23d3b
4
+ data.tar.gz: 62d2d147f332f0b0d8804eb08f7e5030b45b9068b0abb27e77987c04b670188f
5
5
  SHA512:
6
- metadata.gz: 9362e5d687de750ecdbdcbb131739116d3571c58db900fdf4748b6ce480f120670fce8901a8a9cfe83ddd53bbfd5e7866e396caff66e2151dc36a22dbabc3e7f
7
- data.tar.gz: f08197d575142c5c5c16ba4c09e9d96af5ccb5e92fd9b0c11b87fe8431a0cac72bc2462e57ec6080a8c2d8c45b2f92b25a908cdbab35c3116e33f41b46b82559
6
+ metadata.gz: e53218c1b8a8b8ce69463a78f29d27899308edb1a7c383d8b60b09080a1508b5ecb4a29f7527ebaed9c8bbdd080a2c3bf0b4a637e027aaae0c9e592c95979617
7
+ data.tar.gz: f3665ef531ed461202e9cc58214d7b7d386a2e7ab1dbe990609d5414eb2e76f1ec05ab9f54333b578c69329cd5b1da254bc1f06639ec4c82154c0db625dc40db
data/CHANGELOG.md CHANGED
@@ -1,5 +1,57 @@
1
1
  # Release History
2
2
 
3
+ ### 1.29.0 / 2021-03-10
4
+
5
+ #### Features
6
+
7
+ * Drop support for Ruby 2.4 and add support for Ruby 3.0
8
+
9
+ ### 1.28.0 / 2021-03-09
10
+
11
+ #### Features
12
+
13
+ * Add Materialized View support
14
+ * Add Dataset#create_materialized_view
15
+ * Add Table#materialized_view?
16
+ * Add Table#enable_refresh?
17
+ * Add Table#enable_refresh=
18
+ * Add Table#last_refresh_time
19
+ * Add Table#refresh_interval_ms
20
+ * Add Table#refresh_interval_ms=
21
+
22
+ ### 1.27.0 / 2021-02-10
23
+
24
+ #### Features
25
+
26
+ * Add Job#reservation_usage
27
+ * Add Routine#determinism_level
28
+ * Add Routine#determinism_level
29
+ * Add Routine#determinism_level=
30
+ * Add Routine#determinism_level_deterministic?
31
+ * Add Routine#determinism_level_not_deterministic?
32
+ * Add Routine::Updater#determinism_level=
33
+
34
+ ### 1.26.0 / 2021-01-13
35
+
36
+ #### Features
37
+
38
+ * Add support for Hive Partitioning
39
+ * Add hive partitioning options to External::DataSource
40
+ * Add hive partitioning options to LoadJob and LoadJob::Updater
41
+ * Replace google-api-client with google-apis-bigquery_v2
42
+
43
+ ### 1.25.0 / 2020-11-16
44
+
45
+ #### Features
46
+
47
+ * Add routine (UDF) to Dataset::Access
48
+ * Add support for Table ACLS (IAM Policy)
49
+ * feat(bigquery): Add support for Table ACLS
50
+ * Add Bigquery::Policy
51
+ * Add Table#policy
52
+ * Add Table#test_iam_permissions
53
+ * Add Table#update_policy
54
+
3
55
  ### 1.24.0 / 2020-10-29
4
56
 
5
57
  #### Features
data/CONTRIBUTING.md CHANGED
@@ -24,7 +24,7 @@ be able to accept your pull requests.
24
24
  In order to use the google-cloud-bigquery console and run the project's tests,
25
25
  there is a small amount of setup:
26
26
 
27
- 1. Install Ruby. google-cloud-bigquery requires Ruby 2.4+. You may choose to
27
+ 1. Install Ruby. google-cloud-bigquery requires Ruby 2.5+. You may choose to
28
28
  manage your Ruby and gem installations with [RVM](https://rvm.io/),
29
29
  [rbenv](https://github.com/rbenv/rbenv), or
30
30
  [chruby](https://github.com/postmodern/chruby).
@@ -45,7 +45,7 @@ there is a small amount of setup:
45
45
 
46
46
  ```sh
47
47
  $ cd google-cloud-bigquery/
48
- $ bundle exec rake bundleupdate
48
+ $ bundle install
49
49
  ```
50
50
 
51
51
  ## Console
data/LOGGING.md CHANGED
@@ -4,7 +4,7 @@ To enable logging for this library, set the logger for the underlying [Google
4
4
  API
5
5
  Client](https://github.com/google/google-api-ruby-client/blob/master/README.md#logging)
6
6
  library. The logger that you set may be a Ruby stdlib
7
- [`Logger`](https://ruby-doc.org/stdlib-2.4.0/libdoc/logger/rdoc/Logger.html) as
7
+ [`Logger`](https://ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html) as
8
8
  shown below, or a
9
9
  [`Google::Cloud::Logging::Logger`](https://googleapis.dev/ruby/google-cloud-logging/latest)
10
10
  that will write logs to [Stackdriver
@@ -23,8 +23,6 @@ require "date"
23
23
  module Google
24
24
  module Cloud
25
25
  module Bigquery
26
- # rubocop:disable Metrics/ModuleLength
27
-
28
26
  ##
29
27
  # @private
30
28
  #
@@ -378,8 +376,6 @@ module Google
378
376
  (time_obj.to_i * 1000) + (time_obj.nsec / 1_000_000)
379
377
  end
380
378
  end
381
-
382
- # rubocop:enable Metrics/ModuleLength
383
379
  end
384
380
  end
385
381
  end
@@ -144,6 +144,7 @@ module Google
144
144
  ##
145
145
  # @private Create an Updater object.
146
146
  def initialize gapi
147
+ super()
147
148
  @gapi = gapi
148
149
  end
149
150
 
@@ -482,14 +482,14 @@ module Google
482
482
  # puts row[:word]
483
483
  # end
484
484
  #
485
- def all request_limit: nil
485
+ def all request_limit: nil, &block
486
486
  request_limit = request_limit.to_i if request_limit
487
487
 
488
488
  return enum_for :all, request_limit: request_limit unless block_given?
489
489
 
490
490
  results = self
491
491
  loop do
492
- results.each { |r| yield r }
492
+ results.each(&block)
493
493
  if request_limit
494
494
  request_limit -= 1
495
495
  break if request_limit.negative?
@@ -618,15 +618,17 @@ module Google
618
618
  end
619
619
 
620
620
  ##
621
- # Creates a new [view](https://cloud.google.com/bigquery/docs/views)
622
- # table, which is a virtual table defined by the given SQL query.
621
+ # Creates a new view, which is a virtual table defined by the given SQL query.
623
622
  #
624
- # BigQuery's views are logical views, not materialized views, which
625
- # means that the query that defines the view is re-executed every time
626
- # the view is queried. Queries are billed according to the total amount
623
+ # With BigQuery's logical views, the query that defines the view is re-executed
624
+ # every time the view is queried. Queries are billed according to the total amount
627
625
  # of data in all table fields referenced directly or indirectly by the
628
626
  # top-level query. (See {Table#view?} and {Table#query}.)
629
627
  #
628
+ # For materialized views, see {#create_materialized_view}.
629
+ #
630
+ # @see https://cloud.google.com/bigquery/docs/views Creating views
631
+ #
630
632
  # @param [String] table_id The ID of the view table. The ID must contain
631
633
  # only letters (a-z, A-Z), numbers (0-9), or underscores (_). The
632
634
  # maximum length is 1,024 characters.
@@ -667,7 +669,7 @@ module Google
667
669
  # dataset = bigquery.dataset "my_dataset"
668
670
  #
669
671
  # view = dataset.create_view "my_view",
670
- # "SELECT name, age FROM proj.dataset.users"
672
+ # "SELECT name, age FROM proj.dataset.users"
671
673
  #
672
674
  # @example A name and description can be provided:
673
675
  # require "google/cloud/bigquery"
@@ -676,13 +678,18 @@ module Google
676
678
  # dataset = bigquery.dataset "my_dataset"
677
679
  #
678
680
  # view = dataset.create_view "my_view",
679
- # "SELECT name, age FROM proj.dataset.users",
680
- # name: "My View", description: "This is my view"
681
+ # "SELECT name, age FROM proj.dataset.users",
682
+ # name: "My View", description: "This is my view"
681
683
  #
682
684
  # @!group Table
683
685
  #
684
- def create_view table_id, query, name: nil, description: nil,
685
- standard_sql: nil, legacy_sql: nil, udfs: nil
686
+ def create_view table_id,
687
+ query,
688
+ name: nil,
689
+ description: nil,
690
+ standard_sql: nil,
691
+ legacy_sql: nil,
692
+ udfs: nil
686
693
  use_legacy_sql = Convert.resolve_legacy_sql standard_sql, legacy_sql
687
694
  new_view_opts = {
688
695
  table_reference: Google::Apis::BigqueryV2::TableReference.new(
@@ -698,7 +705,81 @@ module Google
698
705
  user_defined_function_resources: udfs_gapi(udfs)
699
706
  )
700
707
  }.delete_if { |_, v| v.nil? }
701
- new_view = Google::Apis::BigqueryV2::Table.new new_view_opts
708
+ new_view = Google::Apis::BigqueryV2::Table.new(**new_view_opts)
709
+
710
+ gapi = service.insert_table dataset_id, new_view
711
+ Table.from_gapi gapi, service
712
+ end
713
+
714
+ ##
715
+ # Creates a new materialized view.
716
+ #
717
+ # Materialized views are precomputed views that periodically cache results of a query for increased performance
718
+ # and efficiency. BigQuery leverages precomputed results from materialized views and whenever possible reads
719
+ # only delta changes from the base table to compute up-to-date results.
720
+ #
721
+ # Queries that use materialized views are generally faster and consume less resources than queries that retrieve
722
+ # the same data only from the base table. Materialized views are helpful to significantly boost performance of
723
+ # workloads that have the characteristic of common and repeated queries.
724
+ #
725
+ # For logical views, see {#create_view}.
726
+ #
727
+ # @see https://cloud.google.com/bigquery/docs/materialized-views-intro Introduction to materialized views
728
+ #
729
+ # @param [String] table_id The ID of the materialized view table. The ID must contain only letters (a-z, A-Z),
730
+ # numbers (0-9), or underscores (_). The maximum length is 1,024 characters.
731
+ # @param [String] query The query that BigQuery executes when the materialized view is referenced.
732
+ # @param [String] name A descriptive name for the table.
733
+ # @param [String] description A user-friendly description of the table.
734
+ # @param [Boolean] enable_refresh Enable automatic refresh of the materialized view when the base table is
735
+ # updated. Optional. The default value is true.
736
+ # @param [Integer] refresh_interval_ms The maximum frequency in milliseconds at which this materialized view
737
+ # will be refreshed. Optional. The default value is `1_800_000` (30 minutes).
738
+ #
739
+ # @return [Google::Cloud::Bigquery::Table] A new table object.
740
+ #
741
+ # @example
742
+ # require "google/cloud/bigquery"
743
+ #
744
+ # bigquery = Google::Cloud::Bigquery.new
745
+ # dataset = bigquery.dataset "my_dataset"
746
+ #
747
+ # materialized_view = dataset.create_materialized_view "my_materialized_view",
748
+ # "SELECT name, age FROM proj.dataset.users"
749
+ #
750
+ # @example Automatic refresh can be disabled:
751
+ # require "google/cloud/bigquery"
752
+ #
753
+ # bigquery = Google::Cloud::Bigquery.new
754
+ # dataset = bigquery.dataset "my_dataset"
755
+ #
756
+ # materialized_view = dataset.create_materialized_view "my_materialized_view",
757
+ # "SELECT name, age FROM proj.dataset.users",
758
+ # enable_refresh: false
759
+ #
760
+ # @!group Table
761
+ #
762
+ def create_materialized_view table_id,
763
+ query,
764
+ name: nil,
765
+ description: nil,
766
+ enable_refresh: nil,
767
+ refresh_interval_ms: nil
768
+ new_view_opts = {
769
+ table_reference: Google::Apis::BigqueryV2::TableReference.new(
770
+ project_id: project_id,
771
+ dataset_id: dataset_id,
772
+ table_id: table_id
773
+ ),
774
+ friendly_name: name,
775
+ description: description,
776
+ materialized_view: Google::Apis::BigqueryV2::MaterializedViewDefinition.new(
777
+ enable_refresh: enable_refresh,
778
+ query: query,
779
+ refresh_interval_ms: refresh_interval_ms
780
+ )
781
+ }.delete_if { |_, v| v.nil? }
782
+ new_view = Google::Apis::BigqueryV2::Table.new(**new_view_opts)
702
783
 
703
784
  gapi = service.insert_table dataset_id, new_view
704
785
  Table.from_gapi gapi, service
@@ -2500,11 +2581,9 @@ module Google
2500
2581
  create_table table_id do |tbl_updater|
2501
2582
  yield tbl_updater if block_given?
2502
2583
  end
2503
- # rubocop:disable Lint/HandleExceptions
2504
2584
  rescue Google::Cloud::AlreadyExistsError
2585
+ # Do nothing if it already exists
2505
2586
  end
2506
- # rubocop:enable Lint/HandleExceptions
2507
-
2508
2587
  sleep 60
2509
2588
  retry
2510
2589
  end
@@ -2547,7 +2626,7 @@ module Google
2547
2626
  return if attributes.empty?
2548
2627
  ensure_service!
2549
2628
  patch_args = Hash[attributes.map { |attr| [attr, @gapi.send(attr)] }]
2550
- patch_gapi = Google::Apis::BigqueryV2::Dataset.new patch_args
2629
+ patch_gapi = Google::Apis::BigqueryV2::Dataset.new(**patch_args)
2551
2630
  patch_gapi.etag = etag if etag
2552
2631
  @gapi = service.patch_dataset dataset_id, patch_gapi
2553
2632
  end
@@ -2676,12 +2755,11 @@ module Google
2676
2755
 
2677
2756
  def load_local_or_uri file, updater
2678
2757
  job_gapi = updater.to_gapi
2679
- job = if local_file? file
2680
- load_local file, job_gapi
2681
- else
2682
- load_storage file, job_gapi
2683
- end
2684
- job
2758
+ if local_file? file
2759
+ load_local file, job_gapi
2760
+ else
2761
+ load_storage file, job_gapi
2762
+ end
2685
2763
  end
2686
2764
 
2687
2765
  def storage_url? files
@@ -2721,6 +2799,7 @@ module Google
2721
2799
  ##
2722
2800
  # @private Create an Updater object.
2723
2801
  def initialize gapi
2802
+ super()
2724
2803
  @updates = []
2725
2804
  @gapi = gapi
2726
2805
  end
@@ -2756,6 +2835,12 @@ module Google
2756
2835
  raise "not implemented in #{self.class}"
2757
2836
  end
2758
2837
 
2838
+ ##
2839
+ # @raise [RuntimeError] not implemented
2840
+ def create_materialized_view(*)
2841
+ raise "not implemented in #{self.class}"
2842
+ end
2843
+
2759
2844
  ##
2760
2845
  # @raise [RuntimeError] not implemented
2761
2846
  def table(*)
@@ -54,6 +54,7 @@ module Google
54
54
  "groupByEmail" => :group_by_email,
55
55
  "iam_member" => :iam_member,
56
56
  "iamMember" => :iam_member,
57
+ "routine" => :routine,
57
58
  "special" => :special_group,
58
59
  "special_group" => :special_group,
59
60
  "specialGroup" => :special_group,
@@ -212,6 +213,33 @@ module Google
212
213
  add_access_role_scope_value :reader, :special, group
213
214
  end
214
215
 
216
+ ##
217
+ # Add access to a routine from a different dataset. Queries executed
218
+ # against that routine will have read access to views/tables/routines
219
+ # in this dataset. Only UDF is supported for now. The role field is
220
+ # not required when this field is set. If that routine is updated by
221
+ # any user, access to the routine needs to be granted again via an
222
+ # update operation.
223
+ #
224
+ # @param [Google::Cloud::Bigquery::Routine] routine A routine object.
225
+ #
226
+ # @example
227
+ # require "google/cloud/bigquery"
228
+ #
229
+ # bigquery = Google::Cloud::Bigquery.new
230
+ # dataset = bigquery.dataset "my_dataset"
231
+ # other_dataset = bigquery.dataset "my_other_dataset", skip_lookup: true
232
+ #
233
+ # routine = other_dataset.routine "my_routine"
234
+ #
235
+ # dataset.access do |access|
236
+ # access.add_reader_routine routine
237
+ # end
238
+ #
239
+ def add_reader_routine routine
240
+ add_access_routine routine
241
+ end
242
+
215
243
  ##
216
244
  # Add reader access to a view.
217
245
  #
@@ -227,9 +255,9 @@ module Google
227
255
  #
228
256
  # bigquery = Google::Cloud::Bigquery.new
229
257
  # dataset = bigquery.dataset "my_dataset"
230
- # other_dataset = bigquery.dataset "my_other_dataset"
258
+ # other_dataset = bigquery.dataset "my_other_dataset", skip_lookup: true
231
259
  #
232
- # view = other_dataset.table "my_view"
260
+ # view = other_dataset.table "my_view", skip_lookup: true
233
261
  #
234
262
  # dataset.access do |access|
235
263
  # access.add_reader_view view
@@ -533,6 +561,28 @@ module Google
533
561
  remove_access_role_scope_value :reader, :special, group
534
562
  end
535
563
 
564
+ ##
565
+ # Remove reader access from a routine from a different dataset.
566
+ #
567
+ # @param [Google::Cloud::Bigquery::Routine] routine A routine object.
568
+ #
569
+ # @example
570
+ # require "google/cloud/bigquery"
571
+ #
572
+ # bigquery = Google::Cloud::Bigquery.new
573
+ # dataset = bigquery.dataset "my_dataset"
574
+ # other_dataset = bigquery.dataset "my_other_dataset", skip_lookup: true
575
+ #
576
+ # routine = other_dataset.routine "my_routine", skip_lookup: true
577
+ #
578
+ # dataset.access do |access|
579
+ # access.remove_reader_routine routine
580
+ # end
581
+ #
582
+ def remove_reader_routine routine
583
+ remove_access_routine routine
584
+ end
585
+
536
586
  ##
537
587
  # Remove reader access from a view.
538
588
  #
@@ -548,9 +598,9 @@ module Google
548
598
  #
549
599
  # bigquery = Google::Cloud::Bigquery.new
550
600
  # dataset = bigquery.dataset "my_dataset"
551
- # other_dataset = bigquery.dataset "my_other_dataset"
601
+ # other_dataset = bigquery.dataset "my_other_dataset", skip_lookup: true
552
602
  #
553
- # view = other_dataset.table "my_view"
603
+ # view = other_dataset.table "my_view", skip_lookup: true
554
604
  #
555
605
  # dataset.access do |access|
556
606
  # access.remove_reader_view view
@@ -849,6 +899,32 @@ module Google
849
899
  lookup_access_role_scope_value :reader, :special, group
850
900
  end
851
901
 
902
+ ##
903
+ # Checks access for a routine from a different dataset. Queries executed
904
+ # against that routine will have read access to views/tables/routines
905
+ # in this dataset. Only UDF is supported for now. The role field is
906
+ # not required when this field is set. If that routine is updated by
907
+ # any user, access to the routine needs to be granted again via an
908
+ # update operation.
909
+ #
910
+ # @param [Google::Cloud::Bigquery::Routine] routine A routine object.
911
+ #
912
+ # @example
913
+ # require "google/cloud/bigquery"
914
+ #
915
+ # bigquery = Google::Cloud::Bigquery.new
916
+ # dataset = bigquery.dataset "my_dataset"
917
+ # other_dataset = bigquery.dataset "my_other_dataset", skip_lookup: true
918
+ #
919
+ # routine = other_dataset.routine "my_routine", skip_lookup: true
920
+ #
921
+ # access = dataset.access
922
+ # access.reader_routine? routine #=> false
923
+ #
924
+ def reader_routine? routine
925
+ lookup_access_routine routine
926
+ end
927
+
852
928
  ##
853
929
  # Checks reader access for a view.
854
930
  #
@@ -864,9 +940,9 @@ module Google
864
940
  #
865
941
  # bigquery = Google::Cloud::Bigquery.new
866
942
  # dataset = bigquery.dataset "my_dataset"
867
- # other_dataset = bigquery.dataset "my_other_dataset"
943
+ # other_dataset = bigquery.dataset "my_other_dataset", skip_lookup: true
868
944
  #
869
- # view = other_dataset.table "my_view"
945
+ # view = other_dataset.table "my_view", skip_lookup: true
870
946
  #
871
947
  # access = dataset.access
872
948
  # access.reader_view? view #=> false
@@ -1118,7 +1194,17 @@ module Google
1118
1194
  @rules.reject!(&find_by_scope_and_value(scope, value))
1119
1195
  # Add new rule for this role, scope, and value
1120
1196
  opts = { role: role, scope => value }
1121
- @rules << Google::Apis::BigqueryV2::Dataset::Access.new(opts)
1197
+ @rules << Google::Apis::BigqueryV2::Dataset::Access.new(**opts)
1198
+ end
1199
+
1200
+ # @private
1201
+ def add_access_routine routine
1202
+ value = routine.routine_ref
1203
+ # Remove existing routine rule, if any
1204
+ @rules.reject!(&find_by_scope_and_resource_ref(:routine, value))
1205
+ # Add new rule for this role, scope, and value
1206
+ opts = { routine: value }
1207
+ @rules << Google::Apis::BigqueryV2::Dataset::Access.new(**opts)
1122
1208
  end
1123
1209
 
1124
1210
  # @private
@@ -1126,10 +1212,10 @@ module Google
1126
1212
  # scope is view, make sure value is in the right format
1127
1213
  value = validate_view value
1128
1214
  # Remove existing view rule, if any
1129
- @rules.reject!(&find_view(value))
1215
+ @rules.reject!(&find_by_scope_and_resource_ref(:view, value))
1130
1216
  # Add new rule for this role, scope, and value
1131
1217
  opts = { view: value }
1132
- @rules << Google::Apis::BigqueryV2::Dataset::Access.new(opts)
1218
+ @rules << Google::Apis::BigqueryV2::Dataset::Access.new(**opts)
1133
1219
  end
1134
1220
 
1135
1221
  # @private
@@ -1144,12 +1230,18 @@ module Google
1144
1230
  )
1145
1231
  end
1146
1232
 
1233
+ # @private
1234
+ def remove_access_routine routine
1235
+ # Remove existing routine rule, if any
1236
+ @rules.reject!(&find_by_scope_and_resource_ref(:routine, routine.routine_ref))
1237
+ end
1238
+
1147
1239
  # @private
1148
1240
  def remove_access_view value
1149
1241
  # scope is view, make sure value is in the right format
1150
1242
  value = validate_view value
1151
1243
  # Remove existing view rule, if any
1152
- @rules.reject!(&find_view(value))
1244
+ @rules.reject!(&find_by_scope_and_resource_ref(:view, value))
1153
1245
  end
1154
1246
 
1155
1247
  # @private
@@ -1162,12 +1254,18 @@ module Google
1162
1254
  !(!@rules.detect(&find_by_role_and_scope_and_value(role, scope, value)))
1163
1255
  end
1164
1256
 
1257
+ # @private
1258
+ def lookup_access_routine routine
1259
+ # Detect routine rule, if any
1260
+ !(!@rules.detect(&find_by_scope_and_resource_ref(:routine, routine.routine_ref)))
1261
+ end
1262
+
1165
1263
  # @private
1166
1264
  def lookup_access_view value
1167
1265
  # scope is view, make sure value is in the right format
1168
1266
  value = validate_view value
1169
1267
  # Detect view rule, if any
1170
- !(!@rules.detect(&find_view(value)))
1268
+ !(!@rules.detect(&find_by_scope_and_resource_ref(:view, value)))
1171
1269
  end
1172
1270
 
1173
1271
  # @private
@@ -1186,11 +1284,11 @@ module Google
1186
1284
  end
1187
1285
  end
1188
1286
 
1189
- # @private
1190
- def find_view value
1287
+ # @private Compare hash representations to find table_ref, routine_ref.
1288
+ def find_by_scope_and_resource_ref scope, value
1191
1289
  lambda do |a|
1192
1290
  h = a.to_h
1193
- h[:view].to_h == value.to_h
1291
+ h[scope].to_h == value.to_h
1194
1292
  end
1195
1293
  end
1196
1294
  end