dina 0.1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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