dina 0.1.0.0

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/lib/dina/authentication/authentication.rb +100 -0
  3. data/lib/dina/casters/array_caster.rb +14 -0
  4. data/lib/dina/casters/multilingual_description.rb +42 -0
  5. data/lib/dina/casters/multilingual_description_caster.rb +13 -0
  6. data/lib/dina/casters/multilingual_title.rb +42 -0
  7. data/lib/dina/casters/multilingual_title_caster.rb +13 -0
  8. data/lib/dina/casters/object_caster.rb +11 -0
  9. data/lib/dina/components/determination.rb +31 -0
  10. data/lib/dina/components/georeference_assertion.rb +26 -0
  11. data/lib/dina/exceptions.rb +4 -0
  12. data/lib/dina/models/acquisition_event.rb +24 -0
  13. data/lib/dina/models/assemblage.rb +27 -0
  14. data/lib/dina/models/attachment.rb +19 -0
  15. data/lib/dina/models/base_model.rb +45 -0
  16. data/lib/dina/models/bucket.rb +8 -0
  17. data/lib/dina/models/collecting_event.rb +76 -0
  18. data/lib/dina/models/collecting_method.rb +23 -0
  19. data/lib/dina/models/collection.rb +40 -0
  20. data/lib/dina/models/derivative.rb +26 -0
  21. data/lib/dina/models/file.rb +40 -0
  22. data/lib/dina/models/identifier.rb +67 -0
  23. data/lib/dina/models/institution.rb +25 -0
  24. data/lib/dina/models/managed_attribute.rb +43 -0
  25. data/lib/dina/models/material_sample.rb +62 -0
  26. data/lib/dina/models/object_store.rb +50 -0
  27. data/lib/dina/models/object_store_managed_attribute.rb +39 -0
  28. data/lib/dina/models/object_subtype.rb +15 -0
  29. data/lib/dina/models/organism.rb +26 -0
  30. data/lib/dina/models/organization.rb +42 -0
  31. data/lib/dina/models/person.rb +43 -0
  32. data/lib/dina/models/preparation_type.rb +23 -0
  33. data/lib/dina/models/project.rb +28 -0
  34. data/lib/dina/models/protocol.rb +25 -0
  35. data/lib/dina/models/storage_unit.rb +26 -0
  36. data/lib/dina/models/storage_unit_type.rb +22 -0
  37. data/lib/dina/models/user.rb +26 -0
  38. data/lib/dina/search/base_search.rb +32 -0
  39. data/lib/dina/search/search.rb +20 -0
  40. data/lib/dina/search/search_autocomplete.rb +26 -0
  41. data/lib/dina/search/search_count.rb +20 -0
  42. data/lib/dina/search/search_mapping.rb +19 -0
  43. data/lib/dina/utils/identifier.rb +62 -0
  44. data/lib/dina/utils/identifier_type.rb +16 -0
  45. data/lib/dina/version.rb +14 -0
  46. data/lib/dina.rb +17 -0
  47. metadata +201 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fb9d082cc5779c2164cc62c37dc96ccb9518172dd7d4906e5e7de7ca9270af35
4
+ data.tar.gz: 0fe67de260e7fab5f9167175dfc85bd8fe64f000887941648d7b003bbab16ab5
5
+ SHA512:
6
+ metadata.gz: b1bc2b324d1c9d970aaba2703f347734dff1624f55104c924779e1ddf5a8f3cc0244403cd18c4de355e13dd7949251db844fc992414fb9652329b6d0e09afcae
7
+ data.tar.gz: c3e36505634d44f74c9068988a90ccdd41bf4a9d8462491131665ffb53af15bfd1fc9d64d2527bf43dd2e352457de4a7f8bf228e29f72cb6069a5717e7e6cf0d
@@ -0,0 +1,100 @@
1
+ # encoding: utf-8
2
+
3
+ module Dina
4
+ module Authentication
5
+
6
+ def self.config(options = {})
7
+ @token_store_file = options[:token_store_file]
8
+ @user = options[:user]
9
+ @password = options[:password]
10
+ @server_name = options[:server_name]
11
+ @client_id = options[:client_id]
12
+ @endpoint_url = options[:endpoint_url]
13
+ @authorization_url = options[:authorization_url]
14
+ @realm = options[:realm]
15
+ Keycloak.auth_server_url = @authorization_url
16
+ Keycloak.realm = @realm
17
+ end
18
+
19
+ def self.header
20
+ if access_token.nil? || refresh_token.nil?
21
+ set_token
22
+ end
23
+
24
+ if Time.now >= Time.parse(auth_expiry)
25
+ renew_token
26
+ end
27
+
28
+ "Bearer " + access_token
29
+ end
30
+
31
+ class << self
32
+ attr_accessor :token_store_file, :authorization_url, :endpoint_url, :client_id, :realm, :server_name
33
+
34
+ private
35
+
36
+ def get_token
37
+ response = Keycloak::Client.get_token(
38
+ @user,
39
+ @password,
40
+ client_id= @client_id,
41
+ secret='')
42
+ JSON.parse(response, symbolize_names: true)
43
+ end
44
+
45
+ def set_token
46
+ json = get_token
47
+ auth_expiry = (Time.now + json[:expires_in].seconds).to_s
48
+ save_token(access_token: json[:access_token], refresh_token: json[:refresh_token], auth_expiry: auth_expiry)
49
+ end
50
+
51
+ def renew_token
52
+ begin
53
+ response = Keycloak::Client.get_token_by_refresh_token(
54
+ refresh_token,
55
+ client_id= @client_id,
56
+ secret='')
57
+ json = JSON.parse(response, symbolize_names: true)
58
+ auth_expiry = (Time.now + json[:expires_in].seconds).to_s
59
+ save_token(access_token: json[:access_token], refresh_token: json[:refresh_token], auth_expiry: auth_expiry)
60
+ rescue
61
+ set_token
62
+ end
63
+ end
64
+
65
+ def access_token
66
+ read_token[:access_token] rescue nil
67
+ end
68
+
69
+ def refresh_token
70
+ read_token[:refresh_token] rescue nil
71
+ end
72
+
73
+ def auth_expiry
74
+ read_token[:auth_expiry] rescue "9999-01-01 00:00:00 -0500"
75
+ end
76
+
77
+ def read_token
78
+ raise TokenStoreFileNotFound unless @token_store_file.instance_of?(String) && ::File.exist?(@token_store_file)
79
+ JSON.parse(::File.read(@token_store_file), symbolize_names: true)[@server_name.to_sym] rescue default_token
80
+ end
81
+
82
+ def default_token
83
+ { access_token: nil, refresh_token: nil, auth_expiry: "9999-01-01 00:00:00 -0500" }
84
+ end
85
+
86
+ def save_token(access_token:, refresh_token:, auth_expiry:)
87
+ raise TokenStoreFileNotFound unless @token_store_file.instance_of?(String) && ::File.exist?(@token_store_file)
88
+ data_hash = JSON.parse(::File.read(@token_store_file), symbolize_names: true) rescue {}
89
+ data_hash[@server_name.to_sym] = {
90
+ access_token: access_token,
91
+ refresh_token: refresh_token,
92
+ auth_expiry: auth_expiry
93
+ }
94
+ ::File.write(@token_store_file, JSON.dump(data_hash))
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,14 @@
1
+
2
+ module Dina
3
+ class ArrayCaster
4
+ def self.cast(value, default)
5
+ if value.is_a?(String)
6
+ [value]
7
+ elsif value.is_a?(Array)
8
+ value
9
+ else
10
+ default
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ module Dina
2
+ class MultilingualDescription
3
+ attr_accessor :descriptions
4
+
5
+ def initialize(opts = {})
6
+ @descriptions = []
7
+ if opts[:descriptions]
8
+ @descriptions = opts[:descriptions]
9
+ end
10
+ if opts[:english]
11
+ self.english_description = opts[:english]
12
+ end
13
+ if opts[:french]
14
+ self.french_description = opts[:french]
15
+ end
16
+ end
17
+
18
+ def english_description=(desc)
19
+ descriptions.delete_if{|o| o[:lang] == "en"}
20
+ descriptions << { lang: "en", desc: desc }
21
+ end
22
+
23
+ def english_description
24
+ descriptions.select{|o| o[:lang] == "en"}.first[:desc]
25
+ end
26
+
27
+ def french_description=(desc)
28
+ descriptions.delete_if{|o| o[:lang] == "fr"}
29
+ descriptions << { lang: "fr", desc: desc }
30
+ end
31
+
32
+ def french_description
33
+ descriptions.select{|o| o[:lang] == "fr"}.first[:desc]
34
+ end
35
+
36
+ def to_hash
37
+ hash = {}
38
+ instance_variables.each { |var| hash[var.to_s.delete('@')] = instance_variable_get(var) }
39
+ hash
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'multilingual_description'
2
+
3
+ module Dina
4
+ class MultilingualDescriptionCaster
5
+ def self.cast(value, default)
6
+ begin
7
+ Dina::MultilingualDescription.new(value).to_hash
8
+ rescue ArgumentError
9
+ { descriptions: [] }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ module Dina
2
+ class MultilingualTitle
3
+ attr_accessor :titles
4
+
5
+ def initialize(opts = {})
6
+ @titles = []
7
+ if opts[:titles]
8
+ @descriptions = opts[:titles]
9
+ end
10
+ if opts[:english]
11
+ self.english_title = opts[:english]
12
+ end
13
+ if opts[:french]
14
+ self.french_title = opts[:french]
15
+ end
16
+ end
17
+
18
+ def english_title=(title)
19
+ titles.delete_if{|o| o[:lang] == "en"}
20
+ titles << { lang: "en", title: desc }
21
+ end
22
+
23
+ def english_title
24
+ titles.select{|o| o[:lang] == "en"}.first[:title]
25
+ end
26
+
27
+ def french_title=(title)
28
+ titles.delete_if{|o| o[:lang] == "fr"}
29
+ titles << { lang: "fr", title: title }
30
+ end
31
+
32
+ def french_title
33
+ titles.select{|o| o[:lang] == "fr"}.first[:title]
34
+ end
35
+
36
+ def to_hash
37
+ hash = {}
38
+ instance_variables.each { |var| hash[var.to_s.delete('@')] = instance_variable_get(var) }
39
+ hash
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'multilingual_title'
2
+
3
+ module Dina
4
+ class MultilingualTitleCaster
5
+ def self.cast(value, default)
6
+ begin
7
+ Dina::MultilingualTitle.new(value).to_hash
8
+ rescue ArgumentError
9
+ { titles: [] }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Dina
2
+ class ObjectCaster
3
+ def self.cast(value, default)
4
+ if value.is_a?(Hash)
5
+ value
6
+ else
7
+ default
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module Dina
2
+ class Determination
3
+ attr_accessor :verbatimScientificName
4
+ attr_accessor :verbatimDeterminer
5
+ attr_accessor :verbatimDate
6
+ attr_accessor :scientificName
7
+ attr_accessor :transcriberRemarks
8
+ attr_accessor :verbatimRemarks
9
+ attr_accessor :determinationRemarks
10
+ attr_accessor :typeStatus
11
+ attr_accessor :typeStatusEvidence
12
+ attr_accessor :determiner #A known UUID for a Person
13
+ attr_accessor :determinedOn
14
+ attr_accessor :qualifier
15
+ attr_accessor :scientificNameSource
16
+ attr_accessor :scientificNameDetails #in the form { classificationPath: "", classificationRanks: "" }
17
+ attr_accessor :isPrimary
18
+ attr_accessor :isFiledAs
19
+ attr_accessor :managedAttributes
20
+
21
+ def initialize
22
+ end
23
+
24
+ def to_hash
25
+ hash = {}
26
+ instance_variables.each { |var| hash[var.to_s.delete('@')] = instance_variable_get(var) }
27
+ hash
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ module Dina
2
+ class GeoreferenceAssertion
3
+ attr_accessor :dwcDecimalLatitude
4
+ attr_accessor :dwcDecimalLongitude
5
+ attr_accessor :dwcCoordinateUncertaintyInMeters
6
+ attr_accessor :dwcGeoreferencedDate
7
+ attr_accessor :georeferencedBy
8
+ attr_accessor :literalGeoreferencedBy
9
+ attr_accessor :dwcGeoreferenceProtocol
10
+ attr_accessor :dwcGeoreferenceSources
11
+ attr_accessor :dwcGeoreferenceRemarks
12
+ attr_accessor :dwcGeodeticDatum #A known UUID for a Person
13
+ attr_accessor :isPrimary
14
+ attr_accessor :dwcGeoreferenceVerificationStatus
15
+
16
+ def initialize
17
+ end
18
+
19
+ def to_hash
20
+ hash = {}
21
+ instance_variables.each { |var| hash[var.to_s.delete('@')] = instance_variable_get(var) }
22
+ hash
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ module Dina
2
+ class DinaException < StandardError; end
3
+ class TokenStoreFileNotFound < DinaException; end
4
+ end
@@ -0,0 +1,24 @@
1
+ require_rel 'base_model'
2
+
3
+ module Dina
4
+ class AcquisitionEvent < BaseModel
5
+ property :id, type: :string, default: SecureRandom.uuid
6
+ property :group, type: :string
7
+ property :receivedDate, type: :string
8
+ property :receptionRemarks, type: :string
9
+ property :isolatedOn, type: :string
10
+ property :isolationRemarks, type: :string
11
+ property :createdBy, type: :string
12
+ property :createdOn, type: :time
13
+
14
+ validates_presence_of :group
15
+
16
+ def self.endpoint_path
17
+ "collection-api/"
18
+ end
19
+
20
+ def self.table_name
21
+ "acquisition-event"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ require_rel 'base_model'
2
+
3
+ module Dina
4
+ class Assemblage < BaseModel
5
+ property :id, type: :string, default: SecureRandom.uuid
6
+ property :group, type: :string
7
+ property :name, type: :string
8
+ property :multilingualTitle, type: :multilingual_title
9
+ property :multilingualDescription, type: :multilingual_description
10
+ property :managedAttributes, type: :object
11
+ property :createdBy, type: :string
12
+ property :createdOn, type: :time
13
+
14
+ has_many :attachment, class_name: "ObjectStore"
15
+
16
+ validates_presence_of :group, :name
17
+
18
+ def self.endpoint_path
19
+ "collection-api/"
20
+ end
21
+
22
+ def self.table_name
23
+ "assemblage"
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ require_rel 'base_model'
2
+
3
+ module Dina
4
+ class Attachment < BaseModel
5
+ property :id, type: :string, default: SecureRandom.uuid
6
+
7
+ belongs_to :material_sample, shallow_path: true
8
+ belongs_to :project, shallow_path: true
9
+
10
+ def self.endpoint_path
11
+ "collection-api/"
12
+ end
13
+
14
+ def self.table_name
15
+ "attachment"
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,45 @@
1
+ module Dina
2
+ class BaseModel < JsonApiClient::Resource
3
+ include JsonApiClient::Helpers::Callbacks
4
+
5
+ self.json_key_format = :camelized_key
6
+ self.paginator = JsonApiClient::Paginating::NestedParamPaginator
7
+
8
+ before_create :on_before_create
9
+
10
+ def self.endpoint_path
11
+ end
12
+
13
+ def self.site
14
+ Dina::Authentication.endpoint_url + endpoint_path
15
+ end
16
+
17
+ def self.custom_headers
18
+ { content_type: "application/vnd.api+json", authorization: Dina::Authentication.header }
19
+ end
20
+
21
+ def self.find_by_group(group, page: 1, per: 50)
22
+ self.where("group.groupName": group).page(page).per(per)
23
+ end
24
+
25
+ def english_description
26
+ if self.respond_to?(:multilingualDescription)
27
+ multilingualDescription[:descriptions].select{|o| o[:lang] == "en"}.first[:desc]
28
+ end
29
+ end
30
+
31
+ def french_description
32
+ if self.respond_to?(:multilingualDescription)
33
+ multilingualDescription[:descriptions].select{|o| o[:lang] == "fr"}.first[:desc]
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def on_before_create
40
+ self.attributes.delete_if { |k, v| v.nil? || v == "" }
41
+ self.attributes = self.attributes.deep_symbolize_keys
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ require_rel 'base_model'
2
+ require_rel 'file'
3
+
4
+ module Dina
5
+ class Bucket < File
6
+ property :id, type: :string, default: SecureRandom.uuid
7
+ end
8
+ end
@@ -0,0 +1,76 @@
1
+ require_rel 'base_model'
2
+
3
+ module Dina
4
+ class CollectingEvent < BaseModel
5
+ property :id, type: :string, default: SecureRandom.uuid
6
+ property :group, type: :string
7
+ property :version, type: :integer
8
+ property :dwcVerbatimCoordinates, type: :string
9
+ property :dwcRecordedBy, type: :string
10
+ property :startEventDateTime, type: :string
11
+ property :endEventDateTime, type: :string
12
+ property :verbatimEventDateTime, type: :string
13
+ property :dwcVerbatimLocality, type: :string
14
+ property :host, type: :string
15
+ property :dwcVerbatimLatitude, type: :string
16
+ property :dwcVerbatimLongitude, type: :string
17
+ property :dwcVerbatimCoordinateSystem, type: :string, default: "decimal degrees"
18
+ property :dwcVerbatimSRS, type: :string, default: "WGS84 (EPSG:4326)"
19
+ property :dwcVerbatimElevation, type: :string
20
+ property :dwcVerbatimDepth, type: :string
21
+ property :dwcOtherRecordNumbers, type: :array, default: []
22
+ property :dwcRecordNumber, type: :string
23
+ property :dwcCountry, type: :string
24
+ property :dwcCountryCode, type: :string
25
+ property :dwcStateProvince, type: :string
26
+ property :habitat, type: :string
27
+ property :dwcMinimumElevationInMeters, type: :float
28
+ property :dwcMinimumDepthInMeters, type: :float
29
+ property :dwcMaximumElevationInMeters, type: :float
30
+ property :dwcMaximumDepthInMeters, type: :float
31
+ property :substrate, type: :string
32
+ property :remarks, type: :string
33
+ property :publiclyReleasable, type: :boolean
34
+ property :notPubliclyReleasableReason, type: :string, default: nil
35
+ property :tags, type: :array, default: []
36
+ property :geographicPlaceNameSource, type: :string
37
+ property :geographicPlaceNameSourceDetail, type: :array, default: [{ sourceID: nil, sourceIdType: nil, sourceURL: nil }]
38
+ property :managedAttributes, type: :array, default: []
39
+ property :geoReferenceAssertions, type: :array, default: []
40
+ property :eventGeom, type: :string
41
+ property :extensionValues, type: :array, default: []
42
+ property :createdBy, type: :string
43
+ property :createdOn, type: :time
44
+
45
+ has_many :collectors, class_name: "Person"
46
+ has_many :attachment, class_name: "Attachment"
47
+
48
+ validates_presence_of :group
49
+
50
+ before_save :on_before_save
51
+
52
+ def self.endpoint_path
53
+ "collection-api/"
54
+ end
55
+
56
+ def self.table_name
57
+ "collecting-event"
58
+ end
59
+
60
+ private
61
+
62
+ def on_before_save
63
+ if self.dwcVerbatimLatitude.nil? && self.dwcVerbatimLongitude.nil?
64
+ self.dwcVerbatimCoordinateSystem = nil
65
+ self.dwcVerbatimSRS = nil
66
+ end
67
+ if self.geographicPlaceNameSource.nil?
68
+ self.geographicPlaceNameSourceDetail = nil
69
+ end
70
+ if !self.geoReferenceAssertions.empty? && self.geoReferenceAssertions[0][:dwcDecimalLatitude].nil?
71
+ self.geoReferenceAssertions = nil
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,23 @@
1
+ require_rel 'base_model'
2
+
3
+ module Dina
4
+ class CollectingMethod < BaseModel
5
+ property :id, type: :string, default: SecureRandom.uuid
6
+ property :group, type: :string
7
+ property :name, type: :string
8
+ property :multilingualDescription, type: :multilingual_description
9
+ property :createdBy, type: :string
10
+ property :createdOn, type: :time
11
+
12
+ validates_presence_of :group
13
+
14
+ def self.endpoint_path
15
+ "collection-api/"
16
+ end
17
+
18
+ def self.table_name
19
+ "collection-method"
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ require_rel 'base_model'
2
+
3
+ module Dina
4
+ class Collection < BaseModel
5
+ property :id, type: :string, default: SecureRandom.uuid
6
+ property :group, type: :string
7
+ property :name, type: :string
8
+ property :code, type: :string
9
+ property :multilingualDescription, type: :multilingual_description
10
+ property :webpage, type: :string
11
+ property :contact, type: :string
12
+ property :address, type: :string
13
+ property :remarks, type: :string
14
+ property :identifiers, type: :array, default: []
15
+ property :createdBy, type: :string
16
+ property :createdOn, type: :time
17
+
18
+ has_one :institution, class_name: "Institution"
19
+ has_one :parent_collection, class_name: "Collection"
20
+
21
+ validates_presence_of :group
22
+
23
+ def self.endpoint_path
24
+ "collection-api/"
25
+ end
26
+
27
+ def self.table_name
28
+ "collection"
29
+ end
30
+
31
+ def self.find_by_name(name)
32
+ where(name: name).all.first
33
+ end
34
+
35
+ def self.find_by_code(code)
36
+ where(code: code).all.first
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ require_rel 'base_model'
2
+
3
+ module Dina
4
+ class Derivative < BaseModel
5
+ property :id, type: :string, default: SecureRandom.uuid
6
+ property :bucket, type: :string
7
+ property :fileIdentifier, type: :string
8
+ property :fileExtension, type: :string
9
+ property :dcType, type: :string
10
+ property :cdFormat, type: :string
11
+ property :acHashFunction, type: :string, default: "SHA-1"
12
+ property :acHashValue, type: :string
13
+ property :derivativeType, type: :string
14
+
15
+ belongs_to :object_store, shallow_path: true
16
+
17
+ def self.endpoint_path
18
+ "objectstore-api/"
19
+ end
20
+
21
+ def self.table_name
22
+ "derivative"
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ require_rel 'base_model'
2
+
3
+ module Dina
4
+ class File < BaseModel
5
+ property :id, type: :string, default: SecureRandom.uuid
6
+
7
+ def self.endpoint_path
8
+ "objectstore-api/"
9
+ end
10
+
11
+ def self.table_name
12
+ "file"
13
+ end
14
+
15
+ #TODO: a "bucket" as part of the path (=table_name) to permit POST with necessary permissions
16
+ #TODO: body not JSON but an asset
17
+
18
+ =begin
19
+
20
+ def initialize(bucket:, file:)
21
+ if !file.is_a?(::File) || !::File.exist?(file)
22
+ raise Exception.new "file is not of class File or does not exist"
23
+ end
24
+ super
25
+ set_headers
26
+ end
27
+
28
+ private
29
+
30
+ def set_headers
31
+ headers = {
32
+ content_type: "application/octet-stream",
33
+ content_disposition: "form-data; name=file; filename=#{::File.basename(file.path)}"
34
+ }
35
+ self.custom_headers.merge(headers)
36
+ end
37
+ =end
38
+
39
+ end
40
+ end