immoscout 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +30 -0
- data/.gitignore +14 -0
- data/.reek +6 -0
- data/.rspec +2 -0
- data/.rubocop.yml +15 -0
- data/.simplecov +3 -0
- data/.travis.yml +20 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +250 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/doc/assets/logo.png +0 -0
- data/doc/assets/project.png +0 -0
- data/doc/assets/project.xcf +0 -0
- data/immoscout.gemspec +47 -0
- data/lib/immoscout.rb +27 -0
- data/lib/immoscout/api/client.rb +27 -0
- data/lib/immoscout/api/connection.rb +34 -0
- data/lib/immoscout/api/request.rb +33 -0
- data/lib/immoscout/configuration.rb +27 -0
- data/lib/immoscout/errors/failed.rb +11 -0
- data/lib/immoscout/models.rb +9 -0
- data/lib/immoscout/models/actions/attachment.rb +82 -0
- data/lib/immoscout/models/actions/contact.rb +75 -0
- data/lib/immoscout/models/actions/publish.rb +33 -0
- data/lib/immoscout/models/actions/real_estate.rb +122 -0
- data/lib/immoscout/models/apartment_buy.rb +90 -0
- data/lib/immoscout/models/base.rb +48 -0
- data/lib/immoscout/models/concerns/modelable.rb +55 -0
- data/lib/immoscout/models/concerns/propertiable.rb +56 -0
- data/lib/immoscout/models/concerns/renderable.rb +63 -0
- data/lib/immoscout/models/contact.rb +59 -0
- data/lib/immoscout/models/document.rb +31 -0
- data/lib/immoscout/models/house_buy.rb +87 -0
- data/lib/immoscout/models/parts/address.rb +26 -0
- data/lib/immoscout/models/parts/api_search_data.rb +19 -0
- data/lib/immoscout/models/parts/contact.rb +18 -0
- data/lib/immoscout/models/parts/coordinate.rb +18 -0
- data/lib/immoscout/models/parts/courtage.rb +19 -0
- data/lib/immoscout/models/parts/energy_certificate.rb +19 -0
- data/lib/immoscout/models/parts/energy_source.rb +17 -0
- data/lib/immoscout/models/parts/firing_type.rb +17 -0
- data/lib/immoscout/models/parts/geo_code.rb +18 -0
- data/lib/immoscout/models/parts/geo_hierarchy.rb +23 -0
- data/lib/immoscout/models/parts/price.rb +20 -0
- data/lib/immoscout/models/parts/publish_channel.rb +17 -0
- data/lib/immoscout/models/parts/real_estate.rb +17 -0
- data/lib/immoscout/models/parts/url.rb +18 -0
- data/lib/immoscout/models/parts/urls.rb +18 -0
- data/lib/immoscout/models/picture.rb +33 -0
- data/lib/immoscout/models/publish.rb +23 -0
- data/lib/immoscout/version.rb +5 -0
- 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
|