immoscout 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 (57) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +30 -0
  3. data/.gitignore +14 -0
  4. data/.reek +6 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +15 -0
  7. data/.simplecov +3 -0
  8. data/.travis.yml +20 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +8 -0
  11. data/LICENSE +21 -0
  12. data/README.md +250 -0
  13. data/Rakefile +8 -0
  14. data/bin/console +14 -0
  15. data/bin/setup +8 -0
  16. data/doc/assets/logo.png +0 -0
  17. data/doc/assets/project.png +0 -0
  18. data/doc/assets/project.xcf +0 -0
  19. data/immoscout.gemspec +47 -0
  20. data/lib/immoscout.rb +27 -0
  21. data/lib/immoscout/api/client.rb +27 -0
  22. data/lib/immoscout/api/connection.rb +34 -0
  23. data/lib/immoscout/api/request.rb +33 -0
  24. data/lib/immoscout/configuration.rb +27 -0
  25. data/lib/immoscout/errors/failed.rb +11 -0
  26. data/lib/immoscout/models.rb +9 -0
  27. data/lib/immoscout/models/actions/attachment.rb +82 -0
  28. data/lib/immoscout/models/actions/contact.rb +75 -0
  29. data/lib/immoscout/models/actions/publish.rb +33 -0
  30. data/lib/immoscout/models/actions/real_estate.rb +122 -0
  31. data/lib/immoscout/models/apartment_buy.rb +90 -0
  32. data/lib/immoscout/models/base.rb +48 -0
  33. data/lib/immoscout/models/concerns/modelable.rb +55 -0
  34. data/lib/immoscout/models/concerns/propertiable.rb +56 -0
  35. data/lib/immoscout/models/concerns/renderable.rb +63 -0
  36. data/lib/immoscout/models/contact.rb +59 -0
  37. data/lib/immoscout/models/document.rb +31 -0
  38. data/lib/immoscout/models/house_buy.rb +87 -0
  39. data/lib/immoscout/models/parts/address.rb +26 -0
  40. data/lib/immoscout/models/parts/api_search_data.rb +19 -0
  41. data/lib/immoscout/models/parts/contact.rb +18 -0
  42. data/lib/immoscout/models/parts/coordinate.rb +18 -0
  43. data/lib/immoscout/models/parts/courtage.rb +19 -0
  44. data/lib/immoscout/models/parts/energy_certificate.rb +19 -0
  45. data/lib/immoscout/models/parts/energy_source.rb +17 -0
  46. data/lib/immoscout/models/parts/firing_type.rb +17 -0
  47. data/lib/immoscout/models/parts/geo_code.rb +18 -0
  48. data/lib/immoscout/models/parts/geo_hierarchy.rb +23 -0
  49. data/lib/immoscout/models/parts/price.rb +20 -0
  50. data/lib/immoscout/models/parts/publish_channel.rb +17 -0
  51. data/lib/immoscout/models/parts/real_estate.rb +17 -0
  52. data/lib/immoscout/models/parts/url.rb +18 -0
  53. data/lib/immoscout/models/parts/urls.rb +18 -0
  54. data/lib/immoscout/models/picture.rb +33 -0
  55. data/lib/immoscout/models/publish.rb +23 -0
  56. data/lib/immoscout/version.rb +5 -0
  57. metadata +268 -0
@@ -0,0 +1,122 @@
1
+ require 'json'
2
+ require_relative '../concerns/modelable'
3
+
4
+ module Immoscout
5
+ module Models
6
+ module Actions
7
+ module RealEstate
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ include Immoscout::Models::Concerns::Modelable
12
+
13
+ self.unpack_collection = proc do |hash|
14
+ hash
15
+ .fetch("realestates.realEstates", {})
16
+ .fetch("realEstateList", {})
17
+ .fetch("realEstateElement", nil)
18
+ end
19
+
20
+ def save
21
+ response = \
22
+ if id
23
+ api.put("user/#{api.user_name}/realestate/#{id}", as_json)
24
+ else
25
+ api.post("user/#{api.user_name}/realestate", as_json)
26
+ end
27
+
28
+ handle_response(response)
29
+ self.id = id_from_response(response) unless id
30
+ self
31
+ end
32
+
33
+ def destroy
34
+ response = api.delete("user/#{api.user_name}/realestate/#{id}")
35
+ handle_response(response)
36
+ self
37
+ end
38
+
39
+ def publish(channel = 10_000)
40
+ publisher = Immoscout::Models::Publish.new(
41
+ real_estate: { id: id },
42
+ publish_channel: { id: channel }
43
+ )
44
+ publisher.save
45
+ publisher
46
+ end
47
+
48
+ def unpublish(channel = 10_000)
49
+ publisher = Immoscout::Models::Publish.new(
50
+ real_estate: { id: id },
51
+ publish_channel: { id: channel }
52
+ )
53
+ publisher.destroy
54
+ publisher
55
+ end
56
+
57
+ def place(type)
58
+ check_placement_type(type)
59
+ response = api.post(
60
+ "user/#{api.user_name}/realestate/#{id}/#{type}"
61
+ )
62
+ handle_response(response)
63
+ self
64
+ end
65
+
66
+ def unplace(type)
67
+ check_placement_type(type)
68
+ response = api.delete(
69
+ "user/#{api.user_name}/realestate/#{id}/#{type}"
70
+ )
71
+ handle_response(response)
72
+ self
73
+ end
74
+
75
+ private
76
+
77
+ def check_placement_type(type)
78
+ raise ArgumentError, "Unknown placement type '#{type}'" unless %w[
79
+ topplacement premiumplacement showcaseplacement
80
+ ].include?(type.to_s)
81
+ end
82
+ end
83
+
84
+ class_methods do
85
+ def find(id)
86
+ response = api.get("user/#{api.user_name}/realestate/#{id}")
87
+ handle_response(response)
88
+ from_raw(response.body)
89
+ end
90
+
91
+ def find_by(hash)
92
+ external_id = hash.symbolize_keys.fetch(:external_id)
93
+ find("ext-#{external_id}")
94
+ end
95
+
96
+ def all
97
+ response = api.get("user/#{api.user_name}/realestate")
98
+ handle_response(response)
99
+ objects = unpack_collection.call(response.body)
100
+ objects
101
+ .map { |object| new(object) }
102
+ .select { |object| object.type =~ /#{name.demodulize}/i }
103
+ end
104
+
105
+ def first
106
+ all.first
107
+ end
108
+
109
+ def last
110
+ all.last
111
+ end
112
+
113
+ def create(hash)
114
+ instance = new(hash)
115
+ instance.save
116
+ instance
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'concerns/renderable'
4
+ require_relative 'concerns/propertiable'
5
+ require_relative 'actions/real_estate'
6
+ require_relative 'parts/api_search_data'
7
+ require_relative 'parts/address'
8
+ require_relative 'parts/contact'
9
+ require_relative 'parts/price'
10
+ require_relative 'parts/courtage'
11
+ require_relative 'parts/energy_source'
12
+
13
+ module Immoscout
14
+ module Models
15
+ class ApartmentBuy < Base
16
+ include Immoscout::Models::Concerns::Renderable
17
+ include Immoscout::Models::Concerns::Propertiable
18
+ include Immoscout::Models::Actions::RealEstate
19
+
20
+ self.json_wrapper = "realestates.apartmentBuy"
21
+
22
+ property :id, alias: :@id
23
+ property :external_id
24
+ property :title
25
+ property :creation_date
26
+ property :last_modification_date
27
+ property :type, alias: :'@xsi.type'
28
+ property :href, alias: :'@xlink.href'
29
+ property :publish_date, alias: :@publish_date
30
+ property :creation, alias: :@creation
31
+ property :modification, alias: :@modification
32
+ property :group_number
33
+ property :real_estate_project_id
34
+ property :address, coerce: Immoscout::Models::Parts::Address
35
+ property :api_search_data, coerce: Immoscout::Models::Parts::ApiSearchData
36
+ property :real_estate_state
37
+ property :description_note
38
+ property :furnishing_note
39
+ property :location_note
40
+ property :other_note
41
+ property :show_address
42
+ property :contact, coerce: Immoscout::Models::Parts::Contact
43
+ property :apartment_type
44
+ property :price, coerce: Immoscout::Models::Parts::Price
45
+ property :courtage, coerce: Immoscout::Models::Parts::Courtage
46
+ property :energy_sources_enev2014,
47
+ coerce: Immoscout::Models::Parts::EnergySource
48
+ property :energy_certificate,
49
+ coerce: Immoscout::Models::Parts::EnergyCertificate
50
+ property :firing_types,
51
+ coerce: Immoscout::Models::Parts::FiringType, array: true
52
+ property :cellar
53
+ property :handicapped_accessible
54
+ property :number_of_parking_spaces
55
+ property :condition
56
+ property :last_refurbishment
57
+ property :interior_quality
58
+ property :construction_year
59
+ property :free_from
60
+ property :heating_type
61
+ property :heating_type_enev2014
62
+ property :building_energy_rating_type
63
+ property :thermal_characteristic
64
+ property :energy_consumption_contains_warm_water
65
+ property :number_of_floors
66
+ property :usable_floor_space
67
+ property :number_of_bed_rooms
68
+ property :number_of_bath_rooms
69
+ property :guest_toilet
70
+ property :parking_space_type
71
+ property :rented
72
+ property :rental_income
73
+ property :listed
74
+ property :parking_space_price
75
+ property :summer_residence_practical
76
+ property :living_space
77
+ property :number_of_rooms
78
+ property :energy_performance_certificate
79
+ # apartment specific properties
80
+ property :floor
81
+ property :lift
82
+ property :assisted_living
83
+ property :built_in_kitchen
84
+ property :balcony
85
+ property :certificate_of_eligibility_needed
86
+ property :garden
87
+ property :service_charge
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Immoscout
4
+ module Models
5
+ class Base
6
+ attr_reader :base
7
+
8
+ def initialize(hash = {})
9
+ define_singleton_method(:base) { hash }
10
+ set_properties
11
+ end
12
+
13
+ private
14
+
15
+ def prepared_hash
16
+ base
17
+ .deep_stringify_keys
18
+ .deep_transform_keys(&:underscore)
19
+ .deep_symbolize_keys
20
+ end
21
+
22
+ def set_properties
23
+ prepared_hash.each do |key, value|
24
+ property = self.class.find_property(key)
25
+ unless property
26
+ # TODO: add optional logger
27
+ # puts "#{self.class.name} - missing property '#{key}'"
28
+ next
29
+ end
30
+ set_property(property, key, value)
31
+ end
32
+ end
33
+
34
+ def set_property(property, key, value)
35
+ coerce_klass = property.fetch(:coerce, nil)
36
+ if coerce_klass
37
+ if property.fetch(:array, false)
38
+ send("#{key}=", value.map { |elem| coerce_klass.new(elem) })
39
+ else
40
+ send("#{key}=", coerce_klass.new(value))
41
+ end
42
+ else
43
+ send("#{key}=", value)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,55 @@
1
+ require 'json'
2
+
3
+ module Immoscout
4
+ module Models
5
+ module Concerns
6
+ module Modelable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ cattr_accessor :json_wrapper, :unpack_collection
11
+ cattr_reader :api, instance_accessor: false do
12
+ Immoscout::Api::Client.instance
13
+ end
14
+
15
+ def api
16
+ self.class.api
17
+ end
18
+
19
+ def handle_response(response)
20
+ self.class.handle_response(response)
21
+ end
22
+
23
+ def id_from_response(response)
24
+ self.class.id_from_response(response)
25
+ end
26
+ end
27
+
28
+ class_methods do
29
+ def unpack(hash)
30
+ hash.values.first
31
+ end
32
+
33
+ def from_raw(raw_hash)
34
+ hash = raw_hash.is_a?(String) ? JSON.parse(raw_hash) : raw_hash
35
+ new(unpack(hash))
36
+ end
37
+
38
+ def handle_response(response)
39
+ return response if response.success?
40
+ raise Immoscout::Errors::Failed, response
41
+ end
42
+
43
+ def id_from_response(response)
44
+ response
45
+ .body
46
+ .fetch("common.messages")
47
+ .first
48
+ .fetch("message", {})
49
+ .fetch("id", nil)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,56 @@
1
+ module Immoscout
2
+ module Models
3
+ module Concerns
4
+ module Propertiable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ cattr_reader :properties, instance_accessor: false do
9
+ {}
10
+ end
11
+
12
+ # :reek:ControlParameter - standard stuff, reek!
13
+ # :reek:TooManyStatements
14
+ def method_missing(method_name, *arguments, &block)
15
+ if method_name =~ /build\_(\w+)/
16
+ match = Regexp.last_match(1).intern
17
+ properties = self.class.properties
18
+ coerce_klass = properties.fetch(match).fetch(:coerce, nil)
19
+ return super if !properties.keys.include?(match) || !coerce_klass
20
+ send("#{match}=", coerce_klass.new)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def attributes
27
+ self.class.properties.keys.sort
28
+ end
29
+
30
+ # :reek:BooleanParameter - standard stuff, reek!
31
+ def respond_to_missing?(method_name, include_private = false)
32
+ method_name.to_s.start_with?('build_') || super
33
+ end
34
+ end
35
+
36
+ class_methods do
37
+ def property(name, **opts)
38
+ attr_accessor(name)
39
+ alias_name = opts.fetch(:alias, false)
40
+ if alias_name
41
+ alias_method alias_name, name
42
+ alias_method "#{opts.fetch(:alias)}=", "#{name}="
43
+ end
44
+ properties[name] = opts
45
+ end
46
+
47
+ def find_property(name)
48
+ properties[name] || properties.values.detect do |value|
49
+ value[:alias] == name
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ require 'json'
2
+
3
+ module Immoscout
4
+ module Models
5
+ module Concerns
6
+ module Renderable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ def as_json
11
+ wrapped? ? to_json_wrapped : to_json_unwrapped
12
+ end
13
+
14
+ def to_json
15
+ as_json.to_json
16
+ end
17
+ alias_method :to_s, :to_json
18
+
19
+ private
20
+
21
+ def wrapped?
22
+ self.class.try(:json_wrapper)
23
+ end
24
+
25
+ def to_h
26
+ self.class.properties.each_with_object({}) do |(key, value), memo|
27
+ # skip if it's readonly and should not be exposed in #as_json
28
+ readonly = value.fetch(:readonly, false)
29
+ next if readonly.try(:call, self) || readonly == true
30
+ # use :alias instead of key as json-key
31
+ property = value.fetch(:alias, key)
32
+ # use :default if present AND value is nil
33
+ rendered = send(key) || value.fetch(:default, nil)
34
+ memo[property] = \
35
+ if rendered.is_a?(Array)
36
+ rendered.map do |elem|
37
+ elem.try(:as_json) || elem
38
+ end
39
+ else
40
+ rendered.try(:as_json) || rendered
41
+ end
42
+ memo
43
+ end
44
+ end
45
+
46
+ def to_json_wrapped
47
+ { self.class.try(:json_wrapper) => to_json_unwrapped }
48
+ end
49
+
50
+ def to_json_unwrapped
51
+ to_h
52
+ .compact
53
+ .stringify_keys
54
+ .deep_transform_keys { |key| key.camelize :lower }
55
+ end
56
+ end
57
+
58
+ class_methods do
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'concerns/renderable'
4
+ require_relative 'concerns/propertiable'
5
+ require_relative 'actions/contact'
6
+ require_relative 'parts/address'
7
+
8
+ module Immoscout
9
+ module Models
10
+ class Contact < Base
11
+ include Immoscout::Models::Concerns::Renderable
12
+ include Immoscout::Models::Concerns::Propertiable
13
+ include Immoscout::Models::Actions::Contact
14
+
15
+ self.json_wrapper = "common.realtorContactDetail"
16
+
17
+ property :id, alias: :@id
18
+ property :email
19
+ property :salutation
20
+ property :firstname
21
+ property :lastname
22
+ property :fax_number
23
+ property :fax_number_country_code,
24
+ readonly: proc { |contact| contact.fax_number.present? }
25
+ property :fax_number_area_code,
26
+ readonly: proc { |contact| contact.fax_number.present? }
27
+ property :fax_number_subscriber,
28
+ readonly: proc { |contact| contact.fax_number.present? }
29
+ property :phone_number
30
+ property :phone_number_country_code,
31
+ readonly: proc { |contact| contact.phone_number.present? }
32
+ property :phone_number_area_code,
33
+ readonly: proc { |contact| contact.phone_number.present? }
34
+ property :phone_number_subscriber,
35
+ readonly: proc { |contact| contact.phone_number.present? }
36
+ property :cell_phone_number
37
+ property :cell_phone_number_country_code,
38
+ readonly: proc { |contact| contact.cell_phone_number.present? }
39
+ property :cell_phone_number_area_code,
40
+ readonly: proc { |contact| contact.cell_phone_number.present? }
41
+ property :cell_phone_number_subscriber,
42
+ readonly: proc { |contact| contact.cell_phone_number.present? }
43
+ property :address, coerce: Immoscout::Models::Parts::Address
44
+ property :country_code
45
+ property :title
46
+ property :addition_name
47
+ property :company
48
+ property :homepage_url
49
+ property :position
50
+ property :office_hours
51
+ property :default_contact
52
+ property :local_partner_contact
53
+ property :business_card_contact
54
+ property :real_estate_reference_count
55
+ property :external_id
56
+ property :show_on_profile_page
57
+ end
58
+ end
59
+ end