reso_transport 1.5.5 → 1.5.7

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: e936cab60989d16c28cef1265638fee97c6d10e94b9d2814de93f69f237c5ffd
4
- data.tar.gz: 0e597dea0da08a2aada6b54cc4a5243e8352b442538d78d584555bb1be05dfcb
3
+ metadata.gz: 49293478c0827ccb4bdca1bb6c9974900f8c0c16853b247487120a0ed3bb79df
4
+ data.tar.gz: c9d8c93979377ed18d22a179bae44175961b44e8641eb091930f99542d1e610f
5
5
  SHA512:
6
- metadata.gz: a4d69fc29b8dde541d9665e80ab60973df81b1f794dbee2e9fffe1877b3fbff3fec62ebe2ff69899c4cc7d4287e7e5f4570d02f66338d8c84d8a4341f0a9aed1
7
- data.tar.gz: ad04f1a60eeecf6731d619be5c16830fd6887d8783f10cbaa44c29a70e6c28491dae55bf49ce29d0f415c6e58b458d73e7bc7161f99767c7e4b402c7f74077d8
6
+ metadata.gz: 4464fe0a9aa0006f25c5eab29dab8422401ac2ebf435b712e65d5727e43606bb0df4fdf895e5a1d6a1e3e44d4937d19c42389577d28568351832ccae64d55ebb
7
+ data.tar.gz: 729d643455ddd8e7a827ddc6aea2a8512f93bc65a3e469993da5d2cd5085ecdc5eed4befee4f376cd9986f51a81d363fdeb042a1f8f51e2e51d7230f265c2d7a
data/.gitignore CHANGED
@@ -13,5 +13,7 @@
13
13
  /log/**
14
14
  /test/vcr_cassettes/**
15
15
  md_cache/**
16
+ ds_cache/**
16
17
  .byebug_history
17
18
  secrets.yml
19
+ /.history/
data/.gitpod.yml ADDED
@@ -0,0 +1,2 @@
1
+ tasks:
2
+ - init: bundle install
data/.rubocop.yml ADDED
@@ -0,0 +1,33 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Layout/FirstHashElementIndentation:
5
+ Enabled: false
6
+
7
+ Layout/MultilineMethodCallIndentation:
8
+ Enabled: false
9
+
10
+ Metrics/AbcSize:
11
+ Max: 20
12
+
13
+ Metrics/BlockLength:
14
+ Exclude:
15
+ - test/**/*
16
+
17
+ Metrics/ClassLength:
18
+ Max: 150
19
+
20
+ Metrics/MethodLength:
21
+ Max: 15
22
+
23
+ Metrics/ModuleLength:
24
+ Max: 150
25
+
26
+ Style/ColonMethodCall:
27
+ Enabled: false
28
+
29
+ Style/Documentation:
30
+ Enabled: false
31
+
32
+ Style/FrozenStringLiteralComment:
33
+ Enabled: false
data/README.md CHANGED
@@ -1,3 +1,6 @@
1
+ [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/wrstudios/reso_transport)
2
+ [![Gem Version](https://badge.fury.io/rb/reso_transport.svg)](https://badge.fury.io/rb/reso_transport)
3
+
1
4
  # ResoTransport
2
5
 
3
6
  A Ruby gem for connecting to and interacting with RESO WebAPI services. Learn more about what that is by checking out the [RESO WebAPI](https://www.reso.org/reso-web-api/) Documentation.
@@ -51,7 +54,7 @@ Or you can set a logger for each specific instance of a client which can be usef
51
54
 
52
55
  ### Getting Connected
53
56
 
54
- There are 2 strategies for authentication.
57
+ There are 2 strategies for authentication.
55
58
 
56
59
  **Bearer Token**
57
60
 
@@ -60,7 +63,8 @@ It's simple to use a static access token if your token never expires:
60
63
  ```ruby
61
64
  @client = ResoTransport::Client.new({
62
65
  md_file: METADATA_CACHE,
63
- endpoint: ENDPOINT_URL
66
+ endpoint: ENDPOINT_URL,
67
+ use_replication_endpoint: false # this is the default and can be ommitted
64
68
  authentication: {
65
69
  access_token: TOKEN,
66
70
  token_type: "Bearer" # this is the default and can be ommitted
@@ -89,7 +93,8 @@ If the connection requires requesting a new token periodically, it's easy to pro
89
93
 
90
94
  This will pre-fetch a token from the provided endpoint when the current token is either non-existent or has expired.
91
95
 
92
-
96
+ The `use_replication_endpoint` flag will append `/replication` to all resource queries if set to `true`. This is required
97
+ by some data sources to query resources beyond 10,000 records.
93
98
 
94
99
  ### Caching Metadata
95
100
 
@@ -108,9 +113,9 @@ If you don't have access to the file system, like on Heroku, or you just don't w
108
113
 
109
114
  ```ruby
110
115
  class MyCacheStore < ResoTransport::MetadataCache
111
-
116
+
112
117
  def read
113
- # read `name` from somewhere
118
+ # read `name` from somewhere
114
119
  end
115
120
 
116
121
  def write(data)
@@ -158,12 +163,25 @@ Once you have a successful connection you can explore what resources are availab
158
163
  #=> {"Property"=>#<ResoTransport::Resource entity_set="Property", schema="ODataService">, "Office"=>#<ResoTransport::Resource entity_set="Office", schema="ODataService">, "Member"=>#<ResoTransport::Resource entity_set="Member", schema="ODataService">}
159
164
 
160
165
  @client.resources["Property"]
161
- #=> #<ResoTransport::Resource entity_set="Property", schema="ODataService">
166
+ #=> #<ResoTransport::Resource entity_set="Property", schema="ODataService">
162
167
 
163
168
  @client.resources["Property"].query.limit(1).results
164
169
  #=> Results Array
165
170
  ```
166
171
 
172
+ If the resource contains localizations you can access those as well.
173
+
174
+ ```ruby
175
+ @client.resources["Property"].localizations
176
+ #=> {"CommercialSale"=>{"Name"=>"CommercialSale", "ResourcePath"=>"/Property?Class=CommercialSale", "Description"=>"Contains data for Commercial searches.", "DateTimeStamp"=>"2021-05-03T18:13:20.643-07:00"}, "Residential"=>{"Name"=>"Residential", "ResourcePath"=>"/Property?Class=Residential", "Description"=>"Contains data for Residential searches.", "DateTimeStamp"=>"2021-05-03T18:13:20.643-07:00"}}
177
+ ```
178
+
179
+ If a resource contains localizations you must select one by name, before querying, like so:
180
+
181
+ ```ruby
182
+ @client.resources["Property"].localization('Residential').query.limit(1).results
183
+ ```
184
+
167
185
  #### Querying
168
186
 
169
187
  ResoTransport provides powerful querying capabilities:
@@ -196,7 +214,7 @@ To see what child records can be expanded look at `expandable`:
196
214
 
197
215
  ```ruby
198
216
  @resource.expandable
199
- #=> [#<struct ResoTransport::Property name="Media", data_type="Collection(RESO.Media)", attrs={"Name"=>"Media", "Type"=>"Collection(RESO.Media)"}, multi=true, enum=nil, complex_type=nil, entity_type=#<struct ResoTransport::EntityType name="Media", base_type=nil, primary_key="MediaKey", schema="CoreLogic.DataStandard.RESO.DD">> ...]
217
+ #=> [#<struct ResoTransport::Property name="Media", data_type="Collection(RESO.Media)", attrs={"Name"=>"Media", "Type"=>"Collection(RESO.Media)"}, multi=true, enum=nil, complex_type=nil, entity_type=#<struct ResoTransport::EntityType name="Media", base_type=nil, primary_key="MediaKey", schema="CoreLogic.DataStandard.RESO.DD">> ...]
200
218
  ```
201
219
 
202
220
  Use `expand` to expand child records with the top level results.
@@ -210,7 +228,7 @@ You have several options to expand multiple child record sets. Each of these wil
210
228
 
211
229
  ```ruby
212
230
  @resource.query.expand("Media", "Office").limit(10).results
213
-
231
+
214
232
  @resource.query.expand(["Media", "Office"]).limit(10).results
215
233
 
216
234
  @resource.query.expand("Media").expand("Office").limit(10).results
@@ -257,7 +275,7 @@ When querying for an enumeration value, you can provide either the system name,
257
275
 
258
276
  ```ruby
259
277
  @resource.query.eq(StandardStatus: "Active Under Contract").limit(1).compile_params
260
- #=> {"$top"=>1, "$filter"=>"StandardStatus eq 'ActiveUnderContract'"}
278
+ #=> {"$top"=>1, "$filter"=>"StandardStatus eq 'ActiveUnderContract'"}
261
279
  ```
262
280
 
263
281
  ## Development
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "reso_transport"
3
+ require 'bundler/setup'
4
+ require 'reso_transport'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -12,19 +12,19 @@ require "reso_transport"
12
12
  #
13
13
 
14
14
  ResoTransport.configure do |c|
15
- c.logger = Logger.new("log/console.log")
15
+ c.logger = Logger.new('log/console.log')
16
16
  end
17
17
 
18
-
19
- require "irb"
18
+ require 'irb'
20
19
  require 'yaml'
21
20
  require 'byebug'
22
21
 
23
- SECRETS = YAML.load_file("secrets.yml")
22
+ SECRETS = YAML.load_file('secrets.yml')
24
23
 
25
- @trestle = ResoTransport::Client.new(SECRETS[:trestle].merge(logger: Logger.new($stdout)))
26
- #@bridge = ResoTransport::Client.new(SECRETS[:bridge].merge(logger: Logger.new($stdout)))
27
- # @spark = ResoTransport::Client.new(SECRETS[:spark])
28
- #@crmls = ResoTransport::Client.new(SECRETS[:crmls])
24
+ SECRETS.each do |name, data|
25
+ data[:logger] = Logger.new($stdout)
26
+ client = ResoTransport::Client.new(data)
27
+ instance_variable_set("@#{name}", client)
28
+ end
29
29
 
30
30
  IRB.start(__FILE__)
data/bin/rake ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
@@ -5,41 +5,30 @@ require 'faraday'
5
5
  require 'json'
6
6
  require 'time'
7
7
 
8
- require "reso_transport/version"
9
- require "reso_transport/configuration"
10
- require "reso_transport/authentication"
11
- require "reso_transport/client"
12
- require "reso_transport/resource"
13
- require "reso_transport/metadata"
14
- require "reso_transport/metadata_cache"
15
- require "reso_transport/metadata_parser"
16
- require "reso_transport/schema"
17
- require "reso_transport/entity_set"
18
- require "reso_transport/entity_type"
19
- require "reso_transport/enum"
20
- require "reso_transport/property"
21
- require "reso_transport/query"
22
-
23
-
24
-
25
- # module Faraday
26
- # module Utils
27
-
28
- # def escape(str)
29
- # str.to_s.gsub(ESCAPE_RE) do |match|
30
- # '%' + match.unpack('H2' * match.bytesize).join('%').upcase
31
- # end.gsub(" ","%20")
32
-
33
- # end
34
- # end
35
- # end
36
-
37
- Faraday::Utils.default_space_encoding = "%20"
8
+ require 'reso_transport/version'
9
+ require 'reso_transport/configuration'
10
+ require 'reso_transport/authentication'
11
+ require 'reso_transport/client'
12
+ require 'reso_transport/resource'
13
+ require 'reso_transport/metadata'
14
+ require 'reso_transport/metadata_cache'
15
+ require 'reso_transport/metadata_parser'
16
+ require 'reso_transport/datasystem'
17
+ require 'reso_transport/datasystem_parser'
18
+ require 'reso_transport/schema'
19
+ require 'reso_transport/entity_set'
20
+ require 'reso_transport/entity_type'
21
+ require 'reso_transport/enum'
22
+ require 'reso_transport/property'
23
+ require 'reso_transport/query'
24
+
25
+ Faraday::Utils.default_space_encoding = '%20'
38
26
 
39
27
  module ResoTransport
40
28
  class Error < StandardError; end
29
+
41
30
  class AccessDenied < StandardError; end
42
- ODATA_TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
31
+ ODATA_TIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'.freeze
43
32
 
44
33
  class << self
45
34
  attr_writer :configuration
@@ -53,8 +42,7 @@ module ResoTransport
53
42
  yield(configuration)
54
43
  end
55
44
 
56
- def self.split_schema_and_class_name(s)
57
- s.to_s.partition(/(\w+)$/).first(2).map {|s| s.sub(/\.$/, '') }
45
+ def self.split_schema_and_class_name(text)
46
+ text.to_s.partition(/(\w+)$/).first(2).map { |s| s.sub(/\.$/, '') }
58
47
  end
59
-
60
48
  end
@@ -1,20 +1,32 @@
1
1
  module ResoTransport
2
2
  module Authentication
3
3
  class FetchTokenAuth < AuthStrategy
4
- attr_reader :connection, :endpoint, :client_id, :client_secret, :grant_type, :scope
5
-
4
+ attr_reader :endpoint,
5
+ :client_id,
6
+ :client_secret,
7
+ :grant_type,
8
+ :scope,
9
+ :username,
10
+ :password
11
+
6
12
  def initialize(options)
7
- @grant_type = options.fetch(:grant_type, "client_credentials")
8
- @scope = options.fetch(:scope, "api")
13
+ super()
14
+
15
+ @grant_type = options.fetch(:grant_type, 'client_credentials')
16
+ @scope = options.fetch(:scope, 'api')
9
17
  @client_id = options.fetch(:client_id)
10
18
  @client_secret = options.fetch(:client_secret)
11
19
  @endpoint = options.fetch(:endpoint)
20
+ @username = options.fetch(:username, nil)
21
+ @password = options.fetch(:password, nil)
22
+ end
12
23
 
13
- @connection = Faraday.new(@endpoint) do |faraday|
24
+ def connection
25
+ @connection ||= Faraday.new(@endpoint) do |faraday|
14
26
  faraday.request :url_encoded
15
27
  faraday.response :logger, ResoTransport.configuration.logger if ResoTransport.configuration.logger
16
28
  faraday.adapter Faraday.default_adapter
17
- faraday.basic_auth @client_id, @client_secret
29
+ faraday.basic_auth client_id, client_secret
18
30
  end
19
31
  end
20
32
 
@@ -30,19 +42,26 @@ module ResoTransport
30
42
  Access.new({
31
43
  access_token: json.fetch('access_token'),
32
44
  expires_in: json.fetch('expires_in', 1 << (1.size * 8 - 2) - 1),
33
- token_type: json.fetch('token_type', "Bearer")
45
+ token_type: json.fetch('token_type', 'Bearer')
34
46
  })
35
47
  end
36
48
 
37
49
  private
38
50
 
39
51
  def auth_params
40
- {
41
- client_id: client_id,
52
+ params = {
53
+ client_id: client_id,
42
54
  client_secret: client_secret,
43
- grant_type: grant_type,
44
- scope: scope
55
+ grant_type: grant_type,
56
+ scope: scope
45
57
  }
58
+
59
+ if grant_type == 'password'
60
+ params[:username] = username
61
+ params[:password] = password
62
+ end
63
+
64
+ params
46
65
  end
47
66
  end
48
67
  end
@@ -0,0 +1,61 @@
1
+ module ResoTransport
2
+ class BaseMetadata
3
+ MIME_TYPES = {
4
+ xml: 'application/xml',
5
+ json: 'application/json'
6
+ }.freeze
7
+
8
+ attr_reader :client
9
+
10
+ def initialize(client)
11
+ @client = client
12
+ @prefix = nil
13
+ @classname = nil
14
+ end
15
+
16
+ def prefix
17
+ raise 'prefix not set' unless @prefix
18
+
19
+ @prefix
20
+ end
21
+
22
+ def classname
23
+ raise 'classname not set' unless @classname
24
+
25
+ @classname
26
+ end
27
+
28
+ def parser
29
+ @parser ||= Object::const_get("#{classname}Parser").new.parse(data)
30
+ end
31
+
32
+ def data
33
+ if cache_file
34
+ cache.read || cache.write(raw)
35
+ else
36
+ raw
37
+ end
38
+ end
39
+
40
+ def cache
41
+ @cache ||= client.send("#{prefix}_cache").new(cache_file)
42
+ end
43
+
44
+ def cache_file
45
+ @cache_file ||= client.send "#{prefix}_file"
46
+ end
47
+
48
+ def raw
49
+ if response.success?
50
+ response.body
51
+ else
52
+ puts response.body
53
+ raise "Error getting #{classname}!"
54
+ end
55
+ end
56
+
57
+ def response
58
+ raise 'Must implement response method'
59
+ end
60
+ end
61
+ end
@@ -1,35 +1,52 @@
1
1
  module ResoTransport
2
2
  class Client
3
- attr_reader :connection, :uid, :vendor, :endpoint, :auth, :md_file, :md_cache
4
-
3
+ attr_reader :connection, :uid, :vendor, :endpoint, :authentication, :md_file, :md_cache, :ds_file, :ds_cache,
4
+ :use_replication_endpoint
5
+
5
6
  def initialize(options)
6
- @endpoint = options.fetch(:endpoint)
7
- @md_file = options.fetch(:md_file, nil)
8
- @authentication = ensure_valid_auth_strategy(options.fetch(:authentication))
9
- @vendor = options.fetch(:vendor, {})
10
- @faraday_options = options.fetch(:faraday_options, {})
11
- @logger = options.fetch(:logger, nil)
12
- @md_cache = options.fetch(:md_cache, ResoTransport::MetadataCache)
13
-
14
- @connection = Faraday.new(@endpoint, @faraday_options) do |faraday|
7
+ @use_replication_endpoint = options.fetch(:use_replication_endpoint, false)
8
+ @endpoint = options.fetch(:endpoint)
9
+ @md_file = options.fetch(:md_file, nil)
10
+ @ds_file = options.fetch(:ds_file, nil)
11
+ @authentication = ensure_valid_auth_strategy(options.fetch(:authentication))
12
+ @vendor = options.fetch(:vendor, {})
13
+ @faraday_options = options.fetch(:faraday_options, {})
14
+ @logger = options.fetch(:logger, nil)
15
+ @md_cache = options.fetch(:md_cache, ResoTransport::MetadataCache)
16
+ @ds_cache = options.fetch(:ds_cache, ResoTransport::MetadataCache)
17
+ @connection = establish_connection
18
+ end
19
+
20
+ def establish_connection
21
+ Faraday.new(@endpoint, @faraday_options) do |faraday|
15
22
  faraday.request :url_encoded
16
23
  faraday.response :logger, @logger || ResoTransport.configuration.logger
17
- #yield faraday if block_given?
18
24
  faraday.use Authentication::Middleware, @authentication
19
- faraday.adapter Faraday.default_adapter #unless faraday.builder.send(:adapter_set?)
25
+ faraday.adapter Faraday.default_adapter # unless faraday.builder.send(:adapter_set?)
20
26
  end
21
27
  end
22
28
 
23
29
  def resources
24
- @resources ||= metadata.entity_sets.map {|es| {es.name => Resource.new(self, es)} }.reduce(:merge!)
30
+ @resources ||= metadata.entity_sets.map { |es| { es.name => resource_for(es) } }.reduce(:merge!)
31
+ end
32
+
33
+ def resource_for(entity_set)
34
+ localizations = {}
35
+ localizations = datasystem.localizations_for(entity_set.entity_type) if metadata.datasystem?
36
+
37
+ Resource.new(self, entity_set, localizations)
25
38
  end
26
39
 
27
40
  def metadata
28
41
  @metadata ||= Metadata.new(self)
29
42
  end
30
43
 
44
+ def datasystem
45
+ @datasystem ||= Datasystem.new(self)
46
+ end
47
+
31
48
  def to_s
32
- %(#<ResoTransport::Client endpoint="#{endpoint}", md_file="#{md_file}">)
49
+ %(#<ResoTransport::Client endpoint="#{endpoint}", md_file="#{md_file}", ds_file="#{ds_file}">)
33
50
  end
34
51
 
35
52
  def inspect
@@ -41,7 +58,7 @@ module ResoTransport
41
58
  def ensure_valid_auth_strategy(options)
42
59
  case options
43
60
  when Hash
44
- if options.has_key?(:endpoint)
61
+ if options.key?(:endpoint)
45
62
  Authentication::FetchTokenAuth.new(options)
46
63
  else
47
64
  Authentication::StaticTokenAuth.new(options)
@@ -50,6 +67,5 @@ module ResoTransport
50
67
  raise ArgumentError, "#{options.inspect} invalid: cannot determine strategy"
51
68
  end
52
69
  end
53
-
54
70
  end
55
71
  end
@@ -0,0 +1,22 @@
1
+ require_relative 'base_metadata'
2
+
3
+ module ResoTransport
4
+ class Datasystem < BaseMetadata
5
+ def initialize(client)
6
+ super client
7
+ @prefix = 'ds'
8
+ @classname = self.class.name
9
+ end
10
+
11
+ def localizations_for(resource_name)
12
+ localizations = parser.resources.dig(resource_name, 'Localizations') || []
13
+ localizations.map { |l| [l['Name'], l] }.to_h
14
+ end
15
+
16
+ def response
17
+ @response ||= client.connection.get('DataSystem') do |req|
18
+ req.headers['Accept'] = 'application/json'
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ module ResoTransport
2
+ class DatasystemParser
3
+ def parse(doc)
4
+ begin
5
+ data = doc.is_a?(File) ? doc.read : doc
6
+ @json = JSON.parse data
7
+ rescue JSON::ParserError => e
8
+ @json = {}
9
+ puts e.message
10
+ end
11
+ self
12
+ end
13
+
14
+ # value ->
15
+ # Resources ->
16
+ # Name ->
17
+ # ResourcePath ->
18
+ # Localizations ->
19
+ # Name ->
20
+ # ResourcePath ->
21
+
22
+ def resources
23
+ @resources ||= @json['value'].map { |v| v['Resources'] }.flatten.compact.map { |r| [r['Name'], r] }.to_h
24
+ end
25
+ end
26
+ end
@@ -1,11 +1,9 @@
1
1
  module ResoTransport
2
2
  EntitySet = Struct.new(:name, :schema, :entity_type) do
3
-
4
3
  def self.from_stream(args)
5
- schema, entity_type = ResoTransport.split_schema_and_class_name(args["EntityType"])
4
+ schema, entity_type = ResoTransport.split_schema_and_class_name(args['EntityType'])
6
5
 
7
- new(args["Name"], schema, entity_type)
6
+ new(args['Name'], schema, entity_type)
8
7
  end
9
-
10
8
  end
11
9
  end
@@ -1,30 +1,29 @@
1
1
  module ResoTransport
2
2
  EntityType = Struct.new(:name, :base_type, :primary_key, :schema) do
3
-
4
3
  def self.from_stream(args)
5
- new(args["Name"], args["BaseType"])
4
+ new(args['Name'], args['BaseType'])
6
5
  end
7
6
 
8
7
  def parse(record)
9
- record.each_pair do |k,v|
8
+ record.each_pair do |k, v|
10
9
  next if v.nil?
11
- if property = (property_map[k] || navigation_property_map[k])
12
- record[k] = property.parse(v)
13
- end
10
+
11
+ property = property_map[k] || navigation_property_map[k]
12
+ record[k] = property.parse(v) if property
14
13
  end
15
14
  end
16
15
 
17
16
  def parse_value(record)
18
- record.each_pair do |k,v|
17
+ record.each_pair do |k, v|
19
18
  next if v.nil?
20
- if property = (property_map[k] || navigation_property_map[k])
21
- record[k] = property.parse(v)
22
- end
19
+
20
+ property = property_map[k] || navigation_property_map[k]
21
+ record[k] = property.parse(v) if property
23
22
  end
24
23
  end
25
24
 
26
25
  def property_map
27
- @property_map ||= properties.inject({}) {|hsh, p| hsh[p.name] = p; hsh }
26
+ @property_map ||= properties.each_with_object({}) { |p, hsh| hsh[p.name] = p; }
28
27
  end
29
28
 
30
29
  def properties
@@ -32,7 +31,7 @@ module ResoTransport
32
31
  end
33
32
 
34
33
  def navigation_property_map
35
- @navigation_property_map ||= navigation_properties.inject({}) {|hsh, p| hsh[p.name] = p; hsh }
34
+ @navigation_property_map ||= navigation_properties.each_with_object({}) { |p, hsh| hsh[p.name] = p; }
36
35
  end
37
36
 
38
37
  def navigation_properties
@@ -42,6 +41,5 @@ module ResoTransport
42
41
  def enumerations
43
42
  @enumerations ||= []
44
43
  end
45
-
46
44
  end
47
45
  end
@@ -1,10 +1,12 @@
1
- module ResoTransport
2
- Metadata = Struct.new(:client) do
1
+ require_relative 'base_metadata'
3
2
 
4
- MIME_TYPES = {
5
- xml: "application/xml",
6
- json: "application/json"
7
- }
3
+ module ResoTransport
4
+ class Metadata < BaseMetadata
5
+ def initialize(client)
6
+ super client
7
+ @prefix = 'md'
8
+ @classname = self.class.name
9
+ end
8
10
 
9
11
  def entity_sets
10
12
  parser.entity_sets
@@ -14,34 +16,14 @@ module ResoTransport
14
16
  parser.schemas
15
17
  end
16
18
 
17
- def parser
18
- @parser ||= MetadataParser.new.parse(get_data)
19
+ def datasystem?
20
+ parser.datasystem?
19
21
  end
20
22
 
21
- def md_cache
22
- @md_cache ||= client.md_cache.new(client.md_file)
23
- end
24
-
25
- def get_data
26
- if client.md_file
27
- md_cache.read || md_cache.write(raw)
28
- else
29
- raw
30
- end
31
- end
32
-
33
- def raw
34
- resp = client.connection.get("$metadata") do |req|
23
+ def response
24
+ @response ||= client.connection.get('$metadata') do |req|
35
25
  req.headers['Accept'] = MIME_TYPES[client.vendor.fetch(:metadata_format, :xml).to_sym]
36
26
  end
37
-
38
- if resp.success?
39
- resp.body
40
- else
41
- puts resp.body
42
- raise "Error getting metadata!"
43
- end
44
27
  end
45
-
46
28
  end
47
29
  end
@@ -7,15 +7,14 @@ module ResoTransport
7
7
  end
8
8
 
9
9
  def read
10
- if File.exist?(name) && File.size(name) > 0
11
- File.new(name)
12
- end
10
+ return nil if !File.exist?(name) || File.size(name).zero?
11
+
12
+ File.new(name)
13
13
  end
14
14
 
15
15
  def write(raw)
16
- File.open(name, "w") {|f| f.write(raw.force_encoding("UTF-8")) } unless raw.length == 0
16
+ File.open(name, 'w') { |f| f.write(raw.force_encoding('UTF-8')) } if raw.length.positive?
17
17
  File.new(name)
18
18
  end
19
-
20
19
  end
21
20
  end
@@ -14,12 +14,14 @@ module ResoTransport
14
14
  @current_complex_type = nil
15
15
  @current_enum_type = nil
16
16
  @current_member = nil
17
+
18
+ @datasystem = nil
17
19
  end
18
20
 
19
21
  def parse(doc)
20
22
  REXML::Document.parse_stream(doc, self)
21
23
  finalize
22
- return self
24
+ self
23
25
  end
24
26
 
25
27
  def finalize
@@ -54,55 +56,62 @@ module ResoTransport
54
56
 
55
57
  def tag_start(name, args)
56
58
  case name
57
- when "Schema"
59
+ when 'Schema'
58
60
  @schemas << ResoTransport::Schema.from_stream(args)
59
- when "EntitySet"
61
+ when 'EntitySet'
60
62
  @entity_sets << ResoTransport::EntitySet.from_stream(args)
61
- when "EntityType"
63
+ when 'EntityType'
62
64
  @current_entity_type = ResoTransport::EntityType.from_stream(args)
63
- when "ComplexType"
65
+ when 'ComplexType'
64
66
  @current_complex_type = ResoTransport::EntityType.from_stream(args)
65
- when "PropertyRef"
67
+ when 'PropertyRef'
66
68
  @current_entity_type.primary_key = args['Name']
67
- when "Property"
68
- @current_entity_type.properties << ResoTransport::Property.from_stream(args.merge(schema: @schemas.last)) if @current_entity_type
69
- @current_complex_type.properties << ResoTransport::Property.from_stream(args.merge(schema: @schemas.last)) if @current_complex_type
70
- when "NavigationProperty"
69
+ when 'Property'
70
+ if @current_entity_type
71
+ @current_entity_type.properties << ResoTransport::Property.from_stream(args.merge(schema: @schemas.last))
72
+ end
73
+ if @current_complex_type
74
+ @current_complex_type.properties << ResoTransport::Property.from_stream(args.merge(schema: @schemas.last))
75
+ end
76
+ when 'NavigationProperty'
71
77
  @current_entity_type.navigation_properties << ResoTransport::Property.from_stream(args)
72
- when "EnumType"
78
+ when 'EnumType'
73
79
  @current_enum_type = ResoTransport::Enum.from_stream(args.merge(schema: @schemas.last))
74
- when "Member"
80
+ when 'Member'
75
81
  @current_member = ResoTransport::Member.from_stream(args)
76
- when "Annotation"
82
+ when 'Annotation'
77
83
  if @current_enum_type && @current_member
78
84
  @current_member.annotation = args['String']
79
- else
80
- if @current_entity_type || @current_complex_type
81
- #raise args.inspect
82
- end
85
+ elsif @current_entity_type || @current_complex_type
86
+ # raise args.inspect
83
87
  end
84
88
  end
85
- rescue => e
89
+ rescue StandardError => e
86
90
  puts e.inspect
87
91
  puts "Error processing Tag: #{[name, args].inspect}"
88
92
  end
89
93
 
90
94
  def tag_end(name)
91
95
  case name
92
- when "EntityType"
96
+ when 'EntityType'
93
97
  @current_entity_type.schema = @schemas.last.namespace
94
98
  @schemas.last.entity_types << @current_entity_type
95
- when "ComplexType"
99
+ when 'ComplexType'
96
100
  @current_complex_type.schema = @schemas.last.namespace
97
101
  @schemas.last.complex_types << @current_complex_type
98
- when "EnumType"
102
+ when 'EnumType'
99
103
  @enumerations << @current_enum_type
100
104
  @current_enum_type = nil
101
- when "Member"
105
+ when 'Member'
102
106
  @current_enum_type.members << @current_member
103
107
  @current_member = nil
104
108
  end
105
109
  end
106
110
 
111
+ def datasystem?
112
+ return @datasystem unless @datasystem.nil?
113
+
114
+ @datasystem = @schemas.any? { |s| s.entity_types.any? { |t| t.name == 'DataSystem' } }
115
+ end
107
116
  end
108
117
  end
@@ -1,23 +1,22 @@
1
1
  module ResoTransport
2
2
  Query = Struct.new(:resource) do
3
-
4
- def all(*contexts, &block)
3
+ def all(*_contexts, &block)
5
4
  new_query_context('and')
6
5
  instance_eval(&block)
7
6
  clear_query_context
8
- return self
7
+ self
9
8
  end
10
9
 
11
10
  def any(&block)
12
11
  new_query_context('or')
13
12
  instance_eval(&block)
14
13
  clear_query_context
15
- return self
14
+ self
16
15
  end
17
16
 
18
- [:eq, :ne, :gt, :ge, :lt, :le].each do |op|
17
+ %i[eq ne gt ge lt le].each do |op|
19
18
  define_method(op) do |conditions|
20
- conditions.each_pair do |k,v|
19
+ conditions.each_pair do |k, v|
21
20
  current_query_context << "#{k} #{op} #{encode_value(k, v)}"
22
21
  end
23
22
  return self
@@ -26,36 +25,36 @@ module ResoTransport
26
25
 
27
26
  def limit(size)
28
27
  options[:top] = size
29
- return self
28
+ self
30
29
  end
31
30
 
32
31
  def offset(size)
33
32
  options[:skip] = size
34
- return self
33
+ self
35
34
  end
36
35
 
37
- def order(field, dir=nil)
38
- options[:orderby] = [field, dir].join(" ").strip
39
- return self
36
+ def order(field, dir = nil)
37
+ options[:orderby] = [field, dir].join(' ').strip
38
+ self
40
39
  end
41
40
 
42
41
  def include_count
43
42
  options[:count] = true
44
- return self
43
+ self
45
44
  end
46
45
 
47
46
  def select(*fields)
48
- os = options.fetch(:select, "").split(",")
49
- options[:select] = (os + Array(fields)).uniq.join(",")
47
+ os = options.fetch(:select, '').split(',')
48
+ options[:select] = (os + Array(fields)).uniq.join(',')
50
49
 
51
- return self
50
+ self
52
51
  end
53
52
 
54
53
  def expand(*names)
55
- ex = options.fetch(:expand, "").split(",")
56
- options[:expand] = (ex + Array(names)).uniq.join(",")
54
+ ex = options.fetch(:expand, '').split(',')
55
+ options[:expand] = (ex + Array(names)).uniq.join(',')
57
56
 
58
- return self
57
+ self
59
58
  end
60
59
 
61
60
  def count
@@ -63,7 +62,7 @@ module ResoTransport
63
62
  limit(1).include_count
64
63
  resp = resource.get(compile_params)
65
64
  parsed_body = JSON.parse(resp.body)
66
- parsed_body.fetch("@odata.count", 0)
65
+ parsed_body.fetch('@odata.count', 0)
67
66
  end
68
67
 
69
68
  def results
@@ -72,21 +71,28 @@ module ResoTransport
72
71
  if resp[:success]
73
72
  resp[:results]
74
73
  else
75
- puts resp.inspect
76
- raise "Request Failed"
74
+ puts resp[:meta]
75
+ raise 'Request Failed'
77
76
  end
78
77
  end
79
78
 
80
79
  def execute
81
80
  resp = resource.get(compile_params)
82
- parsed_body = JSON.parse(resp.body)
83
- results = Array(parsed_body.delete("value"))
84
-
85
- {
86
- success: resp.success? && !parsed_body.has_key?("error"),
87
- meta: parsed_body,
88
- results: resource.parse(results)
89
- }
81
+ if resp.success?
82
+ parsed_body = JSON.parse(resp.body)
83
+ results = Array(parsed_body.delete('value'))
84
+
85
+ {
86
+ success: resp.success? && !parsed_body.key?('error'),
87
+ meta: parsed_body,
88
+ results: resource.parse(results)
89
+ }
90
+ else
91
+ {
92
+ success: false,
93
+ meta: resp.body
94
+ }
95
+ end
90
96
  end
91
97
 
92
98
  def new_query_context(context)
@@ -110,7 +116,7 @@ module ResoTransport
110
116
  end
111
117
 
112
118
  def sub_queries
113
- @sub_queries ||= Hash.new {|h,k| h[k] = { context: 'and', criteria: [] } }
119
+ @sub_queries ||= Hash.new { |h, k| h[k] = { context: 'and', criteria: [] } }
114
120
  end
115
121
 
116
122
  def compile_filters
@@ -120,36 +126,32 @@ module ResoTransport
120
126
 
121
127
  filter_chunks = []
122
128
 
123
- if global && global[:criteria]&.any?
124
- filter_chunks << global[:criteria].join(" #{global[:context]} ")
125
- end
129
+ filter_chunks << global[:criteria].join(" #{global[:context]} ") if global && global[:criteria]&.any?
126
130
 
127
131
  filter_chunks << filter_groups.map do |g|
128
132
  "(#{g[:criteria].join(" #{g[:context]} ")})"
129
- end.join(" and ")
133
+ end.join(' and ')
130
134
 
131
- filter_chunks.reject {|c| c == ""}.join(" and ")
135
+ filter_chunks.reject { |c| c == '' }.join(' and ')
132
136
  end
133
137
 
134
138
  def compile_params
135
139
  params = {}
136
140
 
137
- options.each_pair do |k,v|
141
+ options.each_pair do |k, v|
138
142
  params["$#{k}"] = v
139
143
  end
140
144
 
141
- if !sub_queries.empty?
142
- params["$filter"] = compile_filters
143
- end
145
+ params['$filter'] = compile_filters unless sub_queries.empty?
144
146
 
145
147
  params
146
148
  end
147
149
 
148
- def encode_value(key, v)
150
+ def encode_value(key, val)
149
151
  field = resource.property(key.to_s)
150
152
  raise "Couldn't find property #{key} for #{resource.name}" if field.nil?
151
- field.encode(v)
152
- end
153
153
 
154
+ field.encode(val)
155
+ end
154
156
  end
155
157
  end
@@ -1,6 +1,5 @@
1
1
  module ResoTransport
2
- Resource = Struct.new(:client, :entity_set) do
3
-
2
+ Resource = Struct.new(:client, :entity_set, :localizations, :local) do
4
3
  def query
5
4
  Query.new(self)
6
5
  end
@@ -10,7 +9,7 @@ module ResoTransport
10
9
  end
11
10
 
12
11
  def property(name)
13
- properties.detect {|p| p.name == name }
12
+ properties.detect { |p| p.name == name }
14
13
  end
15
14
 
16
15
  def properties
@@ -20,13 +19,13 @@ module ResoTransport
20
19
  def expandable
21
20
  entity_type.navigation_properties
22
21
  end
23
-
22
+
24
23
  def entity_type
25
- @entity_type ||= schema.entity_types.detect {|et| et.name == entity_set.entity_type }
24
+ @entity_type ||= schema.entity_types.detect { |et| et.name == entity_set.entity_type }
26
25
  end
27
26
 
28
27
  def schema
29
- @schema ||= md.schemas.detect {|s| s.namespace == entity_set.schema }
28
+ @schema ||= md.schemas.detect { |s| s.namespace == entity_set.schema }
30
29
  end
31
30
 
32
31
  def md
@@ -34,15 +33,30 @@ module ResoTransport
34
33
  end
35
34
 
36
35
  def parse(results)
37
- results.map {|r| entity_type.parse(r) }
36
+ results.map { |r| entity_type.parse(r) }
38
37
  end
39
38
 
40
39
  def get(params)
41
- client.connection.get(name, params) do |req|
40
+ client.connection.get(url, params) do |req|
42
41
  req.headers['Accept'] = 'application/json'
43
42
  end
44
43
  end
45
44
 
45
+ def url
46
+ return local['ResourcePath'].gsub(%r{^/}, '') if local
47
+
48
+ raise 'Localization required' if localizations.any? && local.nil?
49
+
50
+ return "#{name}/replication" if client.use_replication_endpoint
51
+
52
+ name
53
+ end
54
+
55
+ def localization(name)
56
+ self.local = localizations[name] if localizations.key?(name)
57
+ self
58
+ end
59
+
46
60
  def to_s
47
61
  %(#<ResoTransport::Resource entity_set="#{name}", schema="#{schema&.namespace}">)
48
62
  end
@@ -50,6 +64,5 @@ module ResoTransport
50
64
  def inspect
51
65
  to_s
52
66
  end
53
-
54
67
  end
55
68
  end
@@ -1,3 +1,3 @@
1
1
  module ResoTransport
2
- VERSION = "1.5.5"
2
+ VERSION = '1.5.7'.freeze
3
3
  end
@@ -1,35 +1,35 @@
1
-
2
- lib = File.expand_path("../lib", __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "reso_transport/version"
3
+ require 'reso_transport/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "reso_transport"
6
+ spec.name = 'reso_transport'
8
7
  spec.version = ResoTransport::VERSION
9
- spec.authors = ["Jon Druse"]
10
- spec.email = ["jon@wrstudios.com"]
8
+ spec.authors = ['Jon Druse']
9
+ spec.email = ['jon@wrstudios.com']
11
10
 
12
- spec.summary = "A utility for consuming RESO Web API connections"
13
- spec.description = "Supports Trestle, Spark, Bridge Interactive, MLS Grid"
14
- spec.homepage = "http://github.com/wrstudios/reso_transport"
15
- spec.license = "MIT"
11
+ spec.summary = 'A utility for consuming RESO Web API connections'
12
+ spec.description = 'Supports Trestle, Spark, Bridge Interactive, MLS Grid'
13
+ spec.homepage = 'http://github.com/wrstudios/reso_transport'
14
+ spec.license = 'MIT'
16
15
 
16
+ spec.required_ruby_version = '>= 2.6'
17
17
 
18
18
  # Specify which files should be added to the gem when it is released.
19
19
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
21
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
22
  end
23
- spec.bindir = "exe"
23
+ spec.bindir = 'exe'
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
- spec.require_paths = ["lib"]
25
+ spec.require_paths = ['lib']
26
26
 
27
- spec.add_dependency "faraday", "~> 1.0.1"
27
+ spec.add_dependency 'faraday', '~> 1.0.1'
28
28
 
29
- spec.add_development_dependency "bundler", "~> 2"
30
- spec.add_development_dependency "rake", "~> 13"
31
- spec.add_development_dependency "minitest", "~> 5.0"
32
- spec.add_development_dependency "minitest-rg", "~> 5.0"
33
- spec.add_development_dependency "vcr", "~> 6.0"
34
- spec.add_development_dependency "byebug"
29
+ spec.add_development_dependency 'bundler', '~> 2'
30
+ spec.add_development_dependency 'byebug', '~> 11'
31
+ spec.add_development_dependency 'minitest', '~> 5.0'
32
+ spec.add_development_dependency 'minitest-rg', '~> 5.0'
33
+ spec.add_development_dependency 'rake', '~> 13'
34
+ spec.add_development_dependency 'vcr', '~> 6.0'
35
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reso_transport
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.5
4
+ version: 1.5.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Druse
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-25 00:00:00.000000000 Z
11
+ date: 2021-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -39,19 +39,19 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rake
42
+ name: byebug
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '13'
47
+ version: '11'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '13'
54
+ version: '11'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: minitest
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -81,33 +81,33 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '5.0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: vcr
84
+ name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '6.0'
89
+ version: '13'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '6.0'
96
+ version: '13'
97
97
  - !ruby/object:Gem::Dependency
98
- name: byebug
98
+ name: vcr
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ">="
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '0'
103
+ version: '6.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '0'
110
+ version: '6.0'
111
111
  description: Supports Trestle, Spark, Bridge Interactive, MLS Grid
112
112
  email:
113
113
  - jon@wrstudios.com
@@ -116,6 +116,8 @@ extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
118
  - ".gitignore"
119
+ - ".gitpod.yml"
120
+ - ".rubocop.yml"
119
121
  - ".ruby-version"
120
122
  - ".travis.yml"
121
123
  - CODE_OF_CONDUCT.md
@@ -125,6 +127,7 @@ files:
125
127
  - README.md
126
128
  - Rakefile
127
129
  - bin/console
130
+ - bin/rake
128
131
  - bin/setup
129
132
  - lib/reso_transport.rb
130
133
  - lib/reso_transport/authentication.rb
@@ -133,8 +136,11 @@ files:
133
136
  - lib/reso_transport/authentication/fetch_token_auth.rb
134
137
  - lib/reso_transport/authentication/middleware.rb
135
138
  - lib/reso_transport/authentication/static_token_auth.rb
139
+ - lib/reso_transport/base_metadata.rb
136
140
  - lib/reso_transport/client.rb
137
141
  - lib/reso_transport/configuration.rb
142
+ - lib/reso_transport/datasystem.rb
143
+ - lib/reso_transport/datasystem_parser.rb
138
144
  - lib/reso_transport/entity_set.rb
139
145
  - lib/reso_transport/entity_type.rb
140
146
  - lib/reso_transport/enum.rb
@@ -160,7 +166,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
160
166
  requirements:
161
167
  - - ">="
162
168
  - !ruby/object:Gem::Version
163
- version: '0'
169
+ version: '2.6'
164
170
  required_rubygems_version: !ruby/object:Gem::Requirement
165
171
  requirements:
166
172
  - - ">="