pricehubble 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88b0e4aa5870b598e3ee8746eadbbe89ad9467f232573bd50c2380053cc0f8b8
4
- data.tar.gz: 8b2906d87ffef7e4eae172f521b792787a0071c018de6eb2090ab4623cb2e78f
3
+ metadata.gz: 9288520abf1c7458b0963010c2ad43859d866ec986008aceb8a070a7dd24c91c
4
+ data.tar.gz: a09346c0774d4dff01634ec7229543993192ed265a2d5fd73313fffc959df61f
5
5
  SHA512:
6
- metadata.gz: '039e27591727c291f4c11644c1b18ea6c8017a5f6313154a6c2c89dcb3883304148c50c37934079a50bf2c5192706907f0b6ad966d21a9eb84a817c24741017f'
7
- data.tar.gz: 991dfca00b532685474147a2d3c3d325560bd18699646e7dedbbd3678135908ba2821014032ba8c0c54b1e854c562053ed2ab39373e91b80e559136bf47eac99
6
+ metadata.gz: d267670a719b5a34cb21be278f91f36a9662651856db82991cc6d7c396aef57e7e9191ab0bffa93f75e67c281692dfd09c9c5bf9489bbfab921b412e561733ae
7
+ data.tar.gz: 2b836e045349d8e55d43f9ba8999b27e8ae63a9e27aaf18fc5c444936230f8f9279720593770cb03a95f2c50269f154fad88e12b7e192c31ae81d0da889cac8f
@@ -1,8 +1,12 @@
1
+ ### 0.4.0
2
+
3
+ * Added initial dossier handling (create, delete, sharing link) (#3)
4
+
1
5
  ### 0.3.0
2
6
 
3
- * Dropped support for Rails <5.2
4
- * Dropped support for Ruby <2.5
5
- * Updated the faraday gem spec to `~> 1.0` (#)
7
+ * Dropped support for Rails <5.2 (#2)
8
+ * Dropped support for Ruby <2.5 (#2)
9
+ * Updated the faraday gem spec to `~> 1.0` (#2)
6
10
 
7
11
  ### 0.2.0
8
12
 
data/README.md CHANGED
@@ -25,6 +25,7 @@ so feel free to send a pull request.
25
25
  - [Use the PriceHubble::Valuation representation](#use-the-pricehubblevaluation-representation)
26
26
  - [Error Handling](#error-handling-1)
27
27
  - [Advanced Request Examples](#advanced-request-examples)
28
+ - [Dossiers](#dossiers)
28
29
  - [Development](#development)
29
30
  - [Contributing](#contributing)
30
31
 
@@ -370,6 +371,56 @@ end
370
371
  # => +-----------+---------------+-------------+-------------+-------------+
371
372
  ```
372
373
 
374
+ ### Dossiers
375
+
376
+ The pricehubble gem allows you to create and delete dossiers for properties.
377
+ The required property data is equal to the valuation request. Additionally the
378
+ pricehubble gem allows you to create sharing links (permalinks) which will link
379
+ to the dossier dashboard application for customers.
380
+
381
+ **Gotcha!** The API allows to update and search for dossiers. Additionally the
382
+ API supports images and logos for dossiers. The pricehubble gem does not
383
+ support this yet.
384
+
385
+ ```ruby
386
+ # The property to create a dossier for
387
+ apartment = {
388
+ location: {
389
+ address: {
390
+ post_code: '22769',
391
+ city: 'Hamburg',
392
+ street: 'Stresemannstr.',
393
+ house_number: '29'
394
+ }
395
+ },
396
+ property_type: { code: :apartment },
397
+ building_year: 1990,
398
+ living_area: 200
399
+ }
400
+
401
+ dossier = PriceHubble::Dossier.new(
402
+ title: 'Customer Dossier for Stresemannstr. 29',
403
+ description: 'Best apartment in the city',
404
+ deal_type: :sale,
405
+ property: apartment,
406
+ country_code: 'DE',
407
+ asking_sale_price: 600_000 # the minimum price the seller is willing to agree
408
+ # valuation_override_sale_price: '', # overwrite the PH value
409
+ # valuation_override_rent_net: '', # overwrite the PH value
410
+ # valuation_override_rent_gross: '', # overwrite the PH value
411
+ # valuation_override_reason_freetext: '' # explain the visitor why
412
+ )
413
+
414
+ # Save the new dossier
415
+ pp dossier.save!
416
+ pp dossier.id
417
+ # => "25de5429-244e-4584-b58e-b0d7428a2377"
418
+
419
+ # Generate a sharing link for the dossier
420
+ pp dossier.link
421
+ # => "https://dash.pricehubble.com/shared/dossier/eyJ0eXAiOiJ..."
422
+ ```
423
+
373
424
  ## Development
374
425
 
375
426
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative './config'
5
+
6
+ # The property to create a dossier for
7
+ apartment = {
8
+ location: {
9
+ address: {
10
+ post_code: '22769',
11
+ city: 'Hamburg',
12
+ street: 'Stresemannstr.',
13
+ house_number: '29'
14
+ }
15
+ },
16
+ property_type: { code: :apartment },
17
+ building_year: 1990,
18
+ living_area: 200
19
+ }
20
+
21
+ dossier = PriceHubble::Dossier.new(
22
+ title: 'Customer Dossier for Stresemannstr. 29',
23
+ description: 'Best apartment in the city',
24
+ deal_type: :sale,
25
+ property: apartment,
26
+ country_code: 'DE',
27
+ asking_sale_price: 600_000 # the minimum price the seller is willing to agree
28
+ # valuation_override_sale_price: '', # overwrite the PH value
29
+ # valuation_override_rent_net: '', # overwrite the PH value
30
+ # valuation_override_rent_gross: '', # overwrite the PH value
31
+ # valuation_override_reason_freetext: '' # explain the visitor why
32
+ )
33
+
34
+ # Save the new dossier
35
+ pp dossier.save!
36
+ pp dossier.id
37
+ # => "25de5429-244e-4584-b58e-b0d7428a2377"
38
+
39
+ # Generate a sharing link for the dossier
40
+ pp dossier.link
41
+ # => "https://dash.pricehubble.com/shared/dossier/eyJ0eXAiOiJ..."
@@ -40,6 +40,7 @@ module PriceHubble
40
40
  autoload :Location, 'pricehubble/entity/location'
41
41
  autoload :Address, 'pricehubble/entity/address'
42
42
  autoload :Coordinates, 'pricehubble/entity/coordinates'
43
+ autoload :Dossier, 'pricehubble/entity/dossier'
43
44
 
44
45
  # Some general purpose utilities
45
46
  module Utils
@@ -77,6 +78,7 @@ module PriceHubble
77
78
  autoload :Base, 'pricehubble/client/base'
78
79
  autoload :Authentication, 'pricehubble/client/authentication'
79
80
  autoload :Valuation, 'pricehubble/client/valuation'
81
+ autoload :Dossiers, 'pricehubble/client/dossiers'
80
82
  end
81
83
 
82
84
  # Separated features of an entity instance
@@ -85,6 +87,7 @@ module PriceHubble
85
87
  autoload :Attributes, 'pricehubble/entity/concern/attributes'
86
88
  autoload :Associations, 'pricehubble/entity/concern/associations'
87
89
  autoload :Client, 'pricehubble/entity/concern/client'
90
+ autoload :Persistence, 'pricehubble/entity/concern/persistence'
88
91
 
89
92
  # Some custom typed attribute helpers
90
93
  module Attributes
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PriceHubble
4
+ module Client
5
+ # A high level client library for the PriceHubble Dossiers API.
6
+ class Dossiers < Base
7
+ # Create a new dossier.
8
+ #
9
+ # @param entity [PriceHubble::Dossier] the entity to use
10
+ # @param args [Hash{Symbol => Mixed}] additional arguments
11
+ # @return [PriceHubble::Dossier, nil] the PriceHubble dossier,
12
+ # or +nil+ on error
13
+ #
14
+ # rubocop:disable Metrics/MethodLength because thats the bare minimum
15
+ # handling is quite complex
16
+ def create_dossier(entity, **args)
17
+ res = connection.post do |req|
18
+ req.path = '/api/v1/dossiers'
19
+ req.body = entity.attributes.compact
20
+ use_default_context(req, :create_dossier)
21
+ use_authentication(req)
22
+ end
23
+ decision(bang: args.fetch(:bang, false)) do |result|
24
+ result.bang(&bang_entity(entity, res, {}))
25
+ result.good(&assign_entity(entity, res))
26
+ successful?(res)
27
+ end
28
+ end
29
+ # rubocop:enable Metrics/MethodLength
30
+
31
+ # Generates a permalink for the specified dossier which will expire after
32
+ # the set number of days.
33
+ #
34
+ # @param entity [PriceHubble::Dossier] the entity to use
35
+ # @param ttl [ActiveSupport::Duration] the time to live for the new link
36
+ # @param locale [String] the user frontend locale
37
+ # @param args [Hash{Symbol => Mixed}] additional arguments
38
+ #
39
+ # rubocop:disable Metrics/MethodLength because thats the bare minimum
40
+ # rubocop:disable Metrics/AbcSize because the decission
41
+ # handling is quite complex
42
+ def share_dossier(entity, ttl:, locale:, **args)
43
+ res = connection.post do |req|
44
+ req.path = '/api/v1/dossiers/links'
45
+ req.body = {
46
+ dossier_id: entity.id,
47
+ days_to_live: ttl.fdiv(1.day.to_i).ceil,
48
+ country_code: entity.country_code,
49
+ locale: locale
50
+ }
51
+ use_default_context(req, :share_dossier)
52
+ use_authentication(req)
53
+ end
54
+ decision(bang: args.fetch(:bang, false)) do |result|
55
+ result.bang(&bang_entity(entity, res, id: entity.try(:id)))
56
+ result.good { res.body.url }
57
+ successful?(res)
58
+ end
59
+ end
60
+ # rubocop:enable Metrics/MethodLength
61
+ # rubocop:enable Metrics/AbcSize
62
+
63
+ # Delete a dossier entity.
64
+ #
65
+ # @param entity [PriceHubble::Dossier] the entity to delete
66
+ # @param args [Hash{Symbol => Mixed}] additional arguments
67
+ #
68
+ # rubocop:disable Metrics/MethodLength because thats the bare minimum
69
+ # rubocop:disable Metrics/AbcSize because the decission
70
+ # handling is quite complex
71
+ def delete_dossier(entity, **args)
72
+ res = connection.delete do |req|
73
+ req.path = "/api/v1/dossiers/#{entity.id}"
74
+ use_default_context(req, :delete_dossier)
75
+ use_authentication(req)
76
+ end
77
+ decision(bang: args.fetch(:bang, false)) do |result|
78
+ result.bang(&bang_entity(entity, res, id: entity.id))
79
+ result.good(&assign_entity(entity, res) do |assigned_entity|
80
+ assigned_entity.mark_as_destroyed.freeze
81
+ end)
82
+ successful?(res)
83
+ end
84
+ end
85
+ # rubocop:enable Metrics/MethodLength
86
+ # rubocop:enable Metrics/AbcSize
87
+
88
+ # Update a dossier entity.
89
+ #
90
+ # TODO: Implement this.
91
+ #
92
+ # @param entity [PriceHubble::Dossier] the entity to update
93
+ # @param args [Hash{Symbol => Mixed}] additional arguments
94
+ # @return [PriceHubble::Dossier, nil] the entity, or +nil+ on error
95
+ def update_dossier(*)
96
+ # PUT dossiers/<dossier_id>
97
+ raise NotImplementedError
98
+ end
99
+
100
+ # Search for dossier entities.
101
+ #
102
+ # TODO: Implement this.
103
+ #
104
+ # @param criteria [Mixed] the search criteria
105
+ # @param args [Hash{Symbol => Mixed}] additional arguments
106
+ # @return [Array<PriceHubble::Dossier>, nil] the entity,
107
+ # or +nil+ on error
108
+ def search_dossiers(*)
109
+ # POST dossiers/search
110
+ raise NotImplementedError
111
+ end
112
+
113
+ # Generate bang method variants
114
+ bangers :create_dossier, :update_dossier, :delete_dossier,
115
+ :share_dossier, :search_dossiers
116
+ end
117
+ end
118
+ end
@@ -18,7 +18,7 @@ module PriceHubble
18
18
 
19
19
  # By definition empty responses (HTTP status 204)
20
20
  # or actual empty bodies should be an empty hash
21
- body = {} if res[:status] == 204 || res[:body].empty?
21
+ body = {} if res[:status] == 204 || res[:body].blank?
22
22
 
23
23
  # Looks like we have some actual data we can wrap
24
24
  res[:body] = \
@@ -4,6 +4,8 @@ module PriceHubble
4
4
  module Client
5
5
  module Utils
6
6
  # Some helpers to work with responses in a general way.
7
+ #
8
+ # rubocop:disable Metrics/BlockLength because of ActiveSupport::Concern
7
9
  module Response
8
10
  extend ActiveSupport::Concern
9
11
 
@@ -53,8 +55,33 @@ module PriceHubble
53
55
  PriceHubble::RequestError.new(nil, res)
54
56
  end
55
57
  end
58
+
59
+ # Perform the assignment of the response to the given entity. This
60
+ # allows a clean usage of the decision flow control for successful
61
+ # requests. Here comes an example:
62
+ #
63
+ # decision do |result|
64
+ # result.good(&assign_entity(entity, res))
65
+ # end
66
+ #
67
+ # @param entity [Hausgold::BaseEntity] the entity instance to handle
68
+ # @param res [Faraday::Response] the response object
69
+ # @return [Proc] the proc which performs the entity handling
70
+ def assign_entity(entity, res, &block)
71
+ lambda do
72
+ entity.assign_attributes(res.body.to_h)
73
+ entity.send(:changes_applied)
74
+ # We need to call +#changed?+ - the +@mutations_from_database+ is
75
+ # unset and this causes issues on subsequent calls to +#changed?+
76
+ # after a freeze (eg. when deleted)
77
+ entity.changed?
78
+ yield(entity) if block
79
+ entity
80
+ end
81
+ end
56
82
  end
57
83
  end
84
+ # rubocop:enable Metrics/BlockLength
58
85
  end
59
86
  end
60
87
  end
@@ -17,6 +17,7 @@ module PriceHubble
17
17
  include PriceHubble::EntityConcern::Attributes
18
18
  include PriceHubble::EntityConcern::Associations
19
19
  include PriceHubble::EntityConcern::Client
20
+ include PriceHubble::EntityConcern::Persistence
20
21
 
21
22
  # We collect all unknown attributes instead of raising while creating a new
22
23
  # instance. The unknown attributes are wrapped inside a
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PriceHubble
4
+ module EntityConcern
5
+ # Map some of the ActiveRecord::Persistence API methods for an entity
6
+ # instance for good compatibility. See: http://bit.ly/2W1rjfF and
7
+ # http://bit.ly/2ARRFYB
8
+ module Persistence
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ # A simple method to query for the state of the entity instance.
13
+ # Returns +false+ whenever the entity or the changes of it were not yet
14
+ # persisted on the remote application. This is helpful for creating new
15
+ # entities from scratch or checking for persisted updates.
16
+ #
17
+ # @return [Boolean] whenever persisted or not
18
+ def persisted?
19
+ return (new_record? ? false : !changed?) \
20
+ if respond_to? :id
21
+
22
+ false
23
+ end
24
+
25
+ # A simple method to query for the state of the entity instance.
26
+ # Returns +false+ whenever the entity is not yet created on the remote
27
+ # application. This is helpful for creating new entities from scratch.
28
+ #
29
+ # @return [Boolean] whenever persisted or not
30
+ def new_record?
31
+ return id.nil? if respond_to? :id
32
+
33
+ true
34
+ end
35
+
36
+ # Mark the entity instance as destroyed.
37
+ #
38
+ # @return [Hausgold::BaseEntity] the instance itself for method chaining
39
+ def mark_as_destroyed
40
+ @destroyed = true
41
+ self
42
+ end
43
+
44
+ # Returns true if this object has been destroyed, otherwise returns
45
+ # false.
46
+ #
47
+ # @return [Boolean] whenever the entity was destroyed or not
48
+ def destroyed?
49
+ @destroyed == true
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PriceHubble
4
+ # The PriceHubble dossier for a single property.
5
+ #
6
+ # @see https://docs.pricehubble.com/#international-dossier-creation
7
+ class Dossier < BaseEntity
8
+ # Configure the client to use
9
+ client :dossiers
10
+
11
+ # Mapped and tracked attributes
12
+ tracked_attr :id, :deal_type, :property, :country_code,
13
+ :title, :description, :asking_sale_price,
14
+ :valuation_override_sale_price, :valuation_override_rent_net,
15
+ :valuation_override_rent_gross,
16
+ :valuation_override_reason_freetext, :logo, :images
17
+
18
+ # Define attribute types for casting
19
+ typed_attr :deal_type, :enum, values: %i[sale rent]
20
+ typed_attr :country_code, :string_inquirer
21
+
22
+ # Associations
23
+ has_one :property, persist: true, initialize: true
24
+
25
+ # Set some defaults when initialized
26
+ after_initialize do
27
+ self.deal_type ||= :sale
28
+ self.country_code ||= 'DE'
29
+ end
30
+
31
+ # Save the dossier.
32
+ #
33
+ # @param args [Hash{Symbol => Mixed}] additional options
34
+ # @return [Boolean] the result state
35
+ def save(**args)
36
+ # When the current entity is already persisted, we send an update
37
+ if id.present?
38
+ # Skip making requests when the current entity is not dirty
39
+ return true unless changed?
40
+
41
+ # The current entity is dirty, so send an update request
42
+ return client.update_dossier(self, **args)
43
+ end
44
+
45
+ # Otherwise we send a new creation request
46
+ client.create_dossier(self, **args) && true
47
+ end
48
+
49
+ # Deletes the instance at the remote application and freezes this
50
+ # instance to reflect that no changes should be made (since they can't
51
+ # be persisted).
52
+ #
53
+ # @param args [Hash{Symbol => Mixed}] addition settings
54
+ # @return [PriceHubble::Dossier, false] whenever the deletion
55
+ # was successful
56
+ def delete(**args)
57
+ client.delete_dossier(self, **args) || false
58
+ end
59
+ alias destroy delete
60
+
61
+ # Create a new dossier share link.
62
+ #
63
+ # @param ttl [ActiveSupport::Duration] the time to live for the new link
64
+ # @param locale [String] the user frontend locale
65
+ # @param args [Hash{Symbol => Mixed}] additional options
66
+ # @return [String] the new dossier frontend link
67
+ def link(ttl: 365.days, locale: 'de_CH', lang: 'de', **args)
68
+ # Make sure the current dossier is saved before we try to generate a link
69
+ return unless save(**args)
70
+
71
+ # Send a "share dossier" request to the service
72
+ url = client.share_dossier(self, ttl: ttl, locale: locale, **args)
73
+ url += "?lang=#{lang}" if url
74
+ url
75
+ end
76
+
77
+ # Generate bang method variants
78
+ bangers :save, :link, :delete, :destroy
79
+ end
80
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module PriceHubble
4
4
  # The version of the +price-hubble+ gem
5
- VERSION = '0.3.0'
5
+ VERSION = '0.4.0'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pricehubble
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hermann Mayer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-09 00:00:00.000000000 Z
11
+ date: 2020-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -357,6 +357,7 @@ files:
357
357
  - doc/examples/authentication.rb
358
358
  - doc/examples/complex_property_valuations.rb
359
359
  - doc/examples/config.rb
360
+ - doc/examples/dossiers.rb
360
361
  - doc/examples/property_valuations_errors.rb
361
362
  - doc/examples/simple_property_valuations.rb
362
363
  - docker-compose.yml
@@ -367,6 +368,7 @@ files:
367
368
  - lib/pricehubble/client.rb
368
369
  - lib/pricehubble/client/authentication.rb
369
370
  - lib/pricehubble/client/base.rb
371
+ - lib/pricehubble/client/dossiers.rb
370
372
  - lib/pricehubble/client/request/data_sanitization.rb
371
373
  - lib/pricehubble/client/request/default_headers.rb
372
374
  - lib/pricehubble/client/response/data_sanitization.rb
@@ -388,7 +390,9 @@ files:
388
390
  - lib/pricehubble/entity/concern/attributes/string_inquirer.rb
389
391
  - lib/pricehubble/entity/concern/callbacks.rb
390
392
  - lib/pricehubble/entity/concern/client.rb
393
+ - lib/pricehubble/entity/concern/persistence.rb
391
394
  - lib/pricehubble/entity/coordinates.rb
395
+ - lib/pricehubble/entity/dossier.rb
392
396
  - lib/pricehubble/entity/location.rb
393
397
  - lib/pricehubble/entity/property.rb
394
398
  - lib/pricehubble/entity/property_conditions.rb