pricehubble 0.3.0 → 0.4.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: 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