cru_lib 0.0.6 → 0.1.2

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: 3ca87f01a10819552466a88828a6765bfcd4ba8c
4
- data.tar.gz: 2e474d95a22698c1d5a2269ea083ed3a99a700bd
2
+ SHA256:
3
+ metadata.gz: 728c78d6c47945aca14a06d298d49c81980d6dc841dee906fca34d5057765513
4
+ data.tar.gz: cbc0424afd867582c214f486acea47017c19ea593b4ac2f3bc3144d66646cd02
5
5
  SHA512:
6
- metadata.gz: ea2d79e896fc7c21e8634082912a4c0956a4d3cf2d487b89f1969da36610e18be47f0646df77d57ca99a5d9314821a0db88a4b205d120f514daf082d06f81f1f
7
- data.tar.gz: 7ae0567d3331b5065c7f6e85e93b93e584b2327758ff92f441ad9a26f4f61179632f8861350e12013180f4e5efae6cdf045615e705a00b36e3ca2dc4d1438ddb
6
+ metadata.gz: 72c7463e6c6df87f0273607c04fe9ffb6fd27353c2fc1617a9610cf95dcc659da78baeb54879f46b9ef32df57ed1985d233154a3e4ba1682ec0f79f034949586
7
+ data.tar.gz: e1dea8dbc805b803e7889970f7ea1f1d1689e790af86a52ad5278406132403a420ddd7ca6a1ae8e5b1813efd4f06ededc2eabed88139738c6ce70af4633b7801
data/.gitignore CHANGED
@@ -21,4 +21,4 @@ tmp
21
21
  *.a
22
22
  mkmf.log
23
23
  .idea
24
- *.iml
24
+ vendor/ruby
data/cru_lib.gemspec CHANGED
@@ -1,4 +1,6 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  lib = File.expand_path('../lib', __FILE__)
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
6
  require 'cru_lib/version'
@@ -8,19 +10,25 @@ Gem::Specification.new do |spec|
8
10
  spec.version = CruLib::VERSION
9
11
  spec.authors = ['Josh Starcher']
10
12
  spec.email = ['josh.starcher@gmail.com']
11
- spec.summary = %q{Misc libraries for Cru}
12
- spec.description = %q{Collection of common ruby logic used by a number of Cru apps}
13
- spec.homepage = ''
13
+ spec.summary = 'Map ActiveRecord to Global Registry'
14
+ spec.description = 'Provides a common interface for mapping ActiveRecord ' \
15
+ 'models to Global Registry entities and relationships.'
16
+ spec.homepage = 'https://github.com/CruGlobal/cru_lib'
14
17
  spec.license = 'MIT'
15
18
 
16
19
  spec.files = `git ls-files -z`.split("\x0")
17
20
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
22
  spec.require_paths = ['lib']
20
- spec.add_dependency 'global_registry'
21
- spec.add_dependency 'active_model_serializers'
22
- spec.add_dependency 'redis'
23
23
 
24
- spec.add_development_dependency 'bundler', '~> 1.6'
25
- spec.add_development_dependency 'rake'
24
+ spec.add_dependency 'global_registry', '~> 1.0'
25
+
26
+ spec.add_runtime_dependency 'rails', '>= 6.0'
27
+ spec.add_runtime_dependency 'activesupport', '>= 6.0'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 2.3'
30
+ spec.add_development_dependency 'rake', '~> 13'
31
+
32
+ spec.add_development_dependency 'rspec-rails', '~> 5'
33
+ spec.add_development_dependency 'sidekiq', '~> 6'
26
34
  end
@@ -0,0 +1,40 @@
1
+ module CruLib
2
+ module GlobalRegistryMasterPersonMethods
3
+ extend ActiveSupport::Concern
4
+ include CruLib::GlobalRegistryMethods
5
+
6
+ included do
7
+ after_commit :retrieve_gr_master_person_id, on: [:create, :update]
8
+ end
9
+
10
+ def retrieve_gr_master_person_id
11
+ return unless respond_to?(:gr_master_person_id)
12
+ async("async_retrieve_gr_master_person_id")
13
+ end
14
+
15
+ def async_retrieve_gr_master_person_id
16
+ fail CruLib::NoGlobalRegistryIdError, "Person #{id} has no global_registry_id; will retry" unless global_registry_id
17
+ begin
18
+ person_entity = GlobalRegistry::Entity.find(global_registry_id, 'filters[owned_by]' => 'mdm')
19
+ rescue RestClient::ResourceNotFound
20
+ Rails.logger.info "GR entity #{global_registry_id} for Person #{id} does not exist; will _not_ retry"
21
+ return
22
+ end
23
+ mdm_entity_id = Array.wrap(person_entity.dig('entity', 'person', 'master_person:relationship'))
24
+ .first # although there should not be more than one
25
+ .try(:[], 'master_person')
26
+ fail CruLib::NoGlobalRegistryMasterPersonError, "GR entity #{global_registry_id} for Person #{id} has no master_person; will retry" unless mdm_entity_id
27
+ update_columns(gr_master_person_id: mdm_entity_id)
28
+ end
29
+
30
+ module ClassMethods
31
+ def skip_fields_for_gr
32
+ super + %w(gr_master_person_id)
33
+ end
34
+ end
35
+ end
36
+
37
+ class NoGlobalRegistryIdError < StandardError; end
38
+
39
+ class NoGlobalRegistryMasterPersonError < StandardError; end
40
+ end
@@ -6,21 +6,20 @@ module CruLib
6
6
  extend ActiveSupport::Concern
7
7
  include CruLib::Async
8
8
 
9
-
10
9
  included do
11
- after_commit :push_to_global_registry
12
- after_destroy :delete_from_global_registry
10
+ after_commit :push_to_global_registry, on: [ :create, :update ]
11
+ after_commit :delete_from_global_registry, on: :destroy
13
12
  end
14
13
 
15
14
  def delete_from_global_registry
16
15
  if global_registry_id
17
- Sidekiq::Client.enqueue(self.class, nil, :async_delete_from_global_registry, global_registry_id)
16
+ Sidekiq::Client.enqueue(self.class, nil, "async_delete_from_global_registry", global_registry_id)
18
17
  end
19
18
  end
20
19
 
21
20
  # Define default push method
22
21
  def push_to_global_registry
23
- async(:async_push_to_global_registry)
22
+ async("async_push_to_global_registry")
24
23
  end
25
24
 
26
25
  def async_push_to_global_registry(parent_id = nil, parent_type = nil, parent = nil)
@@ -43,7 +42,7 @@ module CruLib
43
42
  @attributes_to_push = {}
44
43
  attributes_to_push['client_integration_id'] = id unless self.class.skip_fields_for_gr.include?('client_integration_id')
45
44
  attributes_to_push['client_updated_at'] = updated_at if respond_to?(:updated_at)
46
- attributes.collect {|k, v| @attributes_to_push[k.underscore] = self.class.gr_value(k.underscore, v)}
45
+ attributes.each {|k, v| @attributes_to_push[k.underscore] = self.class.gr_value(k.underscore, v)}
47
46
  @attributes_to_push.select! {|k, v| v.present? && !self.class.skip_fields_for_gr.include?(k)}
48
47
  end
49
48
  @attributes_to_push
@@ -102,7 +101,7 @@ module CruLib
102
101
 
103
102
  columns_to_push.each do |column|
104
103
  unless existing_fields.include?(column[:name])
105
- GlobalRegistry::EntityType.post(entity_type: {name: column[:name], parent_id: entity_type['id'], field_type: column[:type]})
104
+ GlobalRegistry::EntityType.post(entity_type: {name: column[:name], parent_id: entity_type['id'], field_type: column[:field_type]})
106
105
  end
107
106
  end
108
107
  end
@@ -10,7 +10,7 @@ module CruLib
10
10
  # TODO - deleting a relationship is different.
11
11
  def delete_from_global_registry
12
12
  if global_registry_id
13
- Sidekiq::Client.enqueue(self.class, nil, :async_delete_from_global_registry, global_registry_id)
13
+ Sidekiq::Client.enqueue(self.class, nil, "async_delete_from_global_registry", global_registry_id)
14
14
  end
15
15
  end
16
16
 
@@ -46,7 +46,11 @@ module CruLib
46
46
 
47
47
  global_registry_id = Array.wrap(
48
48
  entity[base_object.class.global_registry_entity_type_name]["#{relationship_name}:relationship"]
49
- ).detect { |hash| hash['client_integration_id'] == id.to_s }['relationship_entity_id']
49
+ ).detect do |hash|
50
+ cid = hash['client_integration_id']
51
+ cid = cid['value'] if cid.is_a?(Hash)
52
+ cid == id.to_s
53
+ end['relationship_entity_id']
50
54
 
51
55
  update_column(:global_registry_id, global_registry_id)
52
56
 
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruLib
4
+ class Railtie < Rails::Railtie
5
+ initializer 'crulib_railtie.configure_rollbar' do
6
+ if Module::const_defined? :Rollbar
7
+ ::Rollbar.configure do |config|
8
+ config.exception_level_filters.merge!('CruLib::NoGlobalRegistryIdError' => 'ignore',
9
+ 'CruLib::NoGlobalRegistryMasterPersonError' => 'ignore')
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module CruLib
2
- VERSION = "0.0.6"
2
+ VERSION = '0.1.2'
3
3
  end
data/lib/cru_lib.rb CHANGED
@@ -2,35 +2,8 @@ require 'cru_lib/version'
2
2
  require 'cru_lib/async'
3
3
  require 'cru_lib/global_registry_methods'
4
4
  require 'cru_lib/global_registry_relationship_methods'
5
- require 'cru_lib/access_token'
6
- require 'cru_lib/access_token_serializer'
7
- require 'cru_lib/access_token_protected_concern'
8
- require 'cru_lib/api_error'
9
- require 'cru_lib/api_error_serializer'
10
- require 'redis'
5
+ require 'cru_lib/global_registry_master_person_methods'
6
+ require 'cru_lib/railtie'
11
7
 
12
8
  module CruLib
13
- class << self
14
- attr_accessor :redis_host, :redis_port, :redis_db, :redis_client
15
-
16
- def configure
17
- yield self
18
- end
19
-
20
- def redis_host
21
- @redis_host ||= 'localhost'
22
- end
23
-
24
- def redis_port
25
- @redis_port ||= '6379'
26
- end
27
-
28
- def redis_db
29
- @redis_db ||= 2
30
- end
31
-
32
- def redis_client
33
- Redis.new(:host => CruLib.redis_host, :port => CruLib.redis_port, :db => CruLib.redis_db)
34
- end
35
- end
36
9
  end
@@ -0,0 +1,19 @@
1
+
2
+ # Mock ActiveRecord::RecordNotFound
3
+ #
4
+ module ActiveRecord
5
+ class RecordNotFound < StandardError
6
+ end
7
+ end
8
+
9
+ # ActiveRecord-ish - a mocked ActiveRecord that behaves like a model class with id
10
+ #
11
+ class ActiveRecordIsh
12
+ attr_accessor :id
13
+
14
+ def self.find(id)
15
+ obj = self.new
16
+ obj.id = id
17
+ obj
18
+ end
19
+ end
@@ -0,0 +1,90 @@
1
+
2
+ require 'cru_lib/async'
3
+ require_relative 'async_commons'
4
+
5
+ require 'sidekiq/testing'
6
+ Sidekiq::Testing.inline!
7
+
8
+ # The class includes Sidekiq worker and responds properly to Async calls
9
+ #
10
+ class SidekiqAsync < ActiveRecordIsh
11
+ include Sidekiq::Worker
12
+ include CruLib::Async
13
+ end
14
+
15
+
16
+ describe CruLib::Async do
17
+ describe 'when there is no id' do
18
+ before(:each) do
19
+ @tested = SidekiqAsync.new
20
+ end
21
+
22
+ it 'calls class method when one argument' do
23
+ param_1 = SecureRandom.uuid
24
+
25
+ # Expect class method get called with just one parameter
26
+ #
27
+ expect(SidekiqAsync).to receive(:method_1) do |arg_1, *arg_2|
28
+ expect(arg_1).to eq(param_1)
29
+ expect(arg_2.length).to eq(0)
30
+ end
31
+
32
+ @tested.async("method_1", param_1)
33
+ end
34
+
35
+ it 'calls class method when two arguments' do
36
+ param_1 = SecureRandom.uuid
37
+ param_2 = SecureRandom.uuid
38
+
39
+ # Expect class method get called with two parameters
40
+ #
41
+ expect(SidekiqAsync).to receive(:method_2) do |arg_1, arg_2, *arg_3|
42
+ expect(arg_1).to eq(param_1)
43
+ expect(arg_2).to eq(param_2)
44
+ expect(arg_3.length).to eq(0)
45
+ end
46
+
47
+ @tested.async("method_2", param_1, param_2)
48
+ end
49
+ end
50
+
51
+ describe 'with id' do
52
+ before(:each) do
53
+ @tested = SidekiqAsync.new
54
+ @id = SecureRandom.random_number(100_000)
55
+ @tested.id = @id
56
+ end
57
+
58
+ it 'calls instance method properly when one argument' do
59
+ param_1 = SecureRandom.uuid
60
+
61
+ # Expect instance method get called with one parameter
62
+ # The object's attribute id is properly set
63
+ #
64
+ expect_any_instance_of(SidekiqAsync).to receive(:inst_method_1) do |object, arg_1, *arg_2|
65
+ expect(object.id).to eq(@id)
66
+ expect(arg_1).to eq(param_1)
67
+ expect(arg_2.length).to eq(0)
68
+ end
69
+
70
+ @tested.async("inst_method_1", param_1)
71
+ end
72
+
73
+ it 'calls instance method properly when two arguments' do
74
+ param_1 = SecureRandom.uuid
75
+ param_2 = SecureRandom.uuid
76
+
77
+ # Expect instance method get called with two parameters
78
+ # The object's attribute id is properly set
79
+ #
80
+ expect_any_instance_of(SidekiqAsync).to receive(:inst_method_2) do |object, arg_1, arg_2, *arg_3|
81
+ expect(object.id).to eq(@id)
82
+ expect(arg_1).to eq(param_1)
83
+ expect(arg_2).to eq(param_2)
84
+ expect(arg_3.length).to eq(0)
85
+ end
86
+
87
+ @tested.async("inst_method_2", param_1, param_2)
88
+ end
89
+ end
90
+ end
metadata CHANGED
@@ -1,86 +1,115 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cru_lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Starcher
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-11 00:00:00.000000000 Z
11
+ date: 2022-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: global_registry
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '1.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: '0'
26
+ version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: active_model_serializers
28
+ name: rails
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '6.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '6.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: redis
42
+ name: activesupport
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '6.0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '6.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1.6'
61
+ version: '2.3'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '1.6'
68
+ version: '2.3'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '13'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '5'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sidekiq
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '6'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
81
109
  - !ruby/object:Gem::Version
82
- version: '0'
83
- description: Collection of common ruby logic used by a number of Cru apps
110
+ version: '6'
111
+ description: Provides a common interface for mapping ActiveRecord models to Global
112
+ Registry entities and relationships.
84
113
  email:
85
114
  - josh.starcher@gmail.com
86
115
  executables: []
@@ -94,21 +123,20 @@ files:
94
123
  - Rakefile
95
124
  - cru_lib.gemspec
96
125
  - lib/cru_lib.rb
97
- - lib/cru_lib/access_token.rb
98
- - lib/cru_lib/access_token_protected_concern.rb
99
- - lib/cru_lib/access_token_serializer.rb
100
- - lib/cru_lib/api_error.rb
101
- - lib/cru_lib/api_error_serializer.rb
102
126
  - lib/cru_lib/async.rb
127
+ - lib/cru_lib/global_registry_master_person_methods.rb
103
128
  - lib/cru_lib/global_registry_methods.rb
104
129
  - lib/cru_lib/global_registry_relationship_methods.rb
130
+ - lib/cru_lib/railtie.rb
105
131
  - lib/cru_lib/version.rb
106
132
  - spec/shared_examples_for_global_registry_models.rb
107
- homepage: ''
133
+ - spec/support/async_commons.rb
134
+ - spec/support/async_spec.rb
135
+ homepage: https://github.com/CruGlobal/cru_lib
108
136
  licenses:
109
137
  - MIT
110
138
  metadata: {}
111
- post_install_message:
139
+ post_install_message:
112
140
  rdoc_options: []
113
141
  require_paths:
114
142
  - lib
@@ -123,11 +151,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
151
  - !ruby/object:Gem::Version
124
152
  version: '0'
125
153
  requirements: []
126
- rubyforge_project:
127
- rubygems_version: 2.4.5.1
128
- signing_key:
154
+ rubygems_version: 3.3.0
155
+ signing_key:
129
156
  specification_version: 4
130
- summary: Misc libraries for Cru
157
+ summary: Map ActiveRecord to Global Registry
131
158
  test_files:
132
159
  - spec/shared_examples_for_global_registry_models.rb
133
- has_rdoc:
160
+ - spec/support/async_commons.rb
161
+ - spec/support/async_spec.rb
@@ -1,50 +0,0 @@
1
- require 'securerandom'
2
-
3
- module CruLib
4
- class AccessToken < ActiveModelSerializers::Model
5
- attr_accessor :key_guid, :email, :first_name, :last_name, :token, :pgt
6
-
7
- def initialize(attributes = {})
8
- super
9
- generate_access_token unless attributes['token']
10
- write
11
- end
12
-
13
- class << self
14
-
15
- def read(token)
16
- json = exist?(token)
17
- new(Oj.load(json)) if json
18
- end
19
-
20
- def exist?(token)
21
- redis_client.get(redis_key(token))
22
- end
23
-
24
- def redis_client
25
- @redis_client ||= CruLib.redis_client
26
- end
27
-
28
- def redis_key(token)
29
- ['cru_lib:access_token', token].join(':')
30
- end
31
-
32
- def del(token)
33
- redis_client.del(redis_key(token))
34
- end
35
- end
36
-
37
- private
38
-
39
- def generate_access_token
40
- loop do
41
- attributes[:token] = SecureRandom.uuid.gsub(/\-/, '')
42
- break unless self.class.exist?(attributes[:token])
43
- end
44
- end
45
-
46
- def write
47
- self.class.redis_client.setex(self.class.redis_key(attributes[:token]), 30.minutes.to_i, to_json)
48
- end
49
- end
50
- end
@@ -1,30 +0,0 @@
1
- module CruLib
2
- module AccessTokenProtectedConcern
3
- extend ActiveSupport::Concern
4
-
5
- protected
6
-
7
- def authenticate_request
8
- authenticate_token || render_unauthorized
9
- end
10
-
11
- def authenticate_token
12
- token = oauth_access_token_from_header
13
- return unless oauth_access_token_from_header
14
- @access_token = AccessToken.read(token)
15
- end
16
-
17
- # grabs access_token from header if one is present
18
- def oauth_access_token_from_header
19
- auth_header = request.env['HTTP_AUTHORIZATION'] || ''
20
- match = auth_header.match(/^Bearer\s(.*)/)
21
- return match[1] if match.present?
22
- false
23
- end
24
-
25
- def render_unauthorized
26
- headers['WWW-Authenticate'] = %{CAS realm="Application"}
27
- render_error('Bad token', status: 401)
28
- end
29
- end
30
- end
@@ -1,13 +0,0 @@
1
- module CruLib
2
- class AccessTokenSerializer < ActiveModel::Serializer
3
- attributes :key_guid, :email, :first_name, :last_name, :token
4
-
5
- def _type
6
- 'access_token'
7
- end
8
-
9
- def id
10
- token
11
- end
12
- end
13
- end
@@ -1,5 +0,0 @@
1
- module CruLib
2
- class ApiError < ActiveModelSerializers::Model
3
- attr_accessor :message, :options
4
- end
5
- end
@@ -1,13 +0,0 @@
1
- module CruLib
2
- class ApiErrorSerializer < ActiveModel::Serializer
3
- attributes :message
4
-
5
- def _type
6
- 'api_error'
7
- end
8
-
9
- def id
10
- _type
11
- end
12
- end
13
- end