afasgem 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c5bf46bef866cc1a2121ae0a52298e6beba6d9dd
4
+ data.tar.gz: 056bf5b73c04c3c8f03060763e5ea10e21b791b0
5
+ SHA512:
6
+ metadata.gz: c14c4274605b912888249c0d08b0a8b3c35f60bdce9bfb012e9cfa2c81126807bab61822ee4f155e83bcaf7251bc4c1911528b6f0c664d54c214f4668087e30e
7
+ data.tar.gz: 72541628f918b44920f72e8613f171ecd1a0e456d83d1f25ff6240364a0980c91e29d341780ad350e1b1ecb4dc24ba92d6bad83aad1e907b56cf2abf92fe9be8
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ /spec/afas_env.rb
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in afasgem.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Sprint ICT, Subhi Dweik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # Afasgem
2
+
3
+ This very creatively named get wraps communication with AFAS get and updateconnectors
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'afasgem'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install afasgem
20
+
21
+ ## Usage
22
+
23
+ ### General config
24
+
25
+ This gem can be configured using the well known ruby configure block
26
+
27
+ ```
28
+ # Start the config block
29
+ Afasgem.configuration do |config|
30
+ # The WSDL url of the AFAS getconnector
31
+ # Probable format: https://<Environment number>.afasonlineconnector.nl/ProfitServices/AppConnectorGet.asmx?WSDL
32
+ config.getconnector_url = 'getconnectorurl'
33
+
34
+ # The WSDL url of the AFAS updateconnector
35
+ # Probable format: https://<Environment number>.afasonlineconnector.nl/ProfitServices/AppConnectorUpdate.asmx?WSDL
36
+ config.updateconnector_url = 'updateconnectorurl'
37
+
38
+ # The WSDL url of the AFAS updateconnector
39
+ # Probable format: ?
40
+ config.dataconnector_url = 'dataconnectorurl'
41
+
42
+ # The token to use
43
+ config.token = 'token'
44
+
45
+ # The number of rows to fetch when not specified
46
+ config.default_results = 150
47
+
48
+ # Whether to log all requests to the console
49
+ config.debug = true
50
+ end
51
+ ```
52
+
53
+ To start communicating with AFAS you need to construct a connector using the
54
+ `Afasgem.getconnector_factory` and `Afasgem.updateconnector_factory`. Both take
55
+ the connectorId (the name of the connector, for example `Profit_Debtor`) as an
56
+ argument
57
+
58
+ This will return a configured get or updateconnector instance that is ready to communicate.
59
+
60
+ ### Getconnector
61
+
62
+ Create a getconnector using the factory after configuring afasgem
63
+
64
+ ```
65
+ conn = Afasgem.getconnector_factory('Profit_Debtor')
66
+ ```
67
+
68
+ #### Fetching data
69
+
70
+ The easiest way to get data out of a connector is to simply call `get_data`.
71
+ This will return an array of results, each in a hash. This hash will have the
72
+ AFAS field names as keys.
73
+
74
+ ```
75
+ conn.get_data # [{result 1}, {result 2}]
76
+ ```
77
+
78
+ The data xml is available using `get_data_xml`. This is the xml that contains
79
+ the data itself. Not the full SOAP request
80
+
81
+ `get_result` will return the full response as a hash. This also is not the full
82
+ SOAP request but just the interesting part of the response
83
+
84
+ When using `get_result`, `get_data`, or `get_data_xml`, the response
85
+ is cached and new requests will only be done when something was changed using
86
+ the pagination handlers below.
87
+
88
+ Calling `execute` issues a new request, even if nothing was changed
89
+
90
+ To get all data at once, the `get_all_results` method is available. This will
91
+ fetch all rows in a connector. Sadly there is no way to tell how many records
92
+ there are in total before fetching them all.
93
+
94
+
95
+
96
+ #### Handling pagination
97
+
98
+ ```
99
+ conn.take(n) # Configures the connector to fetch n records at a time
100
+ conn.skip(n) # Configures the connector to skip the first n records
101
+ conn.page(n) # Provides a simple way to get the nth page of results, 1 indexed
102
+ conn.next # Gets the next page of results
103
+ conn.previous # Gets the previous page of results
104
+ ```
105
+
106
+ All of the above methods provide fluent interfaced, meaning they can be chained.
107
+
108
+ ```
109
+ # Will fetch result 15-25 since we are fetching 10 results a page,
110
+ # skipping the first 5 and then going to page 2
111
+ conn.take(10).skip(5).next.get_data
112
+ ```
113
+
114
+ #### Filtering
115
+
116
+ This gem also wraps the filter functionality of the AFAS api. When adding a filter it is added with an and respective to the filters already in place. To filter using an OR you have to explicitly call `add_or`
117
+
118
+ ```
119
+ # Filter where Field == 'val'
120
+ conn.add_filter('Field', FilterOperators::EQUAL, 'val')
121
+
122
+ # Filter where Field == 'val' && Field2 == 'val2'
123
+ conn.add_filter('Field', FilterOperators::EQUAL, 'val')
124
+ .add_filter('Field2', FilterOperators::EQUAL, 'val2')
125
+
126
+ # Filter where Field == 'val' || Field2 == 'val2'
127
+ conn.add_filter('Field', FilterOperators::EQUAL, 'val')
128
+ .add_or
129
+ .add_filter('Field2', FilterOperators::EQUAL, 'val2')
130
+
131
+ # Filter where (Field == 'val' && Field2 == 'val2') || (Field3 == 'val3')
132
+ conn.add_filter('Field', FilterOperators::EQUAL, 'val')
133
+ .add_filter('Field2', FilterOperators::EQUAL, 'val2')
134
+ .add_or
135
+ .add_filter('Field3', FilterOperators::EQUAL, 'val3')
136
+ ```
137
+
138
+ To clear the filters in place use `clear_filters`
139
+
140
+ Available operators:
141
+ ```
142
+ EQUAL # Value is exactly equal
143
+ LARGER_OR_EQUAL # Value is larger than or equal
144
+ SMALLER_OR_EQUAL # Value is smaller than or equal
145
+ LARGER_THAN # Value is larger than
146
+ SMALLER_THAN # Value is smaller than
147
+ LIKE # Text contains value
148
+ NOT_EQUAL # Value is not equal
149
+ EMPTY # Field is null
150
+ NOT_EMPTY # Field is not null
151
+ STARTS_WITH # Text starts with
152
+ NOT_LIKE # Text does not contain
153
+ NOT_STARTS_WITH # Text does not start with
154
+ ENDS_WITH # Text ends with
155
+ NOT_ENDS_WITH # Text does not end with
156
+ ```
157
+
158
+ ## Updateconnector data
159
+
160
+ To view the fields an updateconnector accepts, there is `bin/connector_format`. This script takes a few params and then lists the fields the passed updateconnector would accept
161
+
162
+ ```
163
+ Usage: connector_format [options]
164
+ -u, --username NAME AFAS username
165
+ -p, --password PASSWORD AFAS password
166
+ -c, --connector CONNECTOR Name of the UpdateConnector
167
+ -e, --environment ENVIRONMENT Name of the environment
168
+ -h, --help Show this message
169
+ ```
170
+
171
+ ## Updateconnector
172
+
173
+ To Actually write data to the AFAS api you can use the `updateconnector_factory`. This factory will return an `UpdateConnector` object that has the following methods:
174
+ ```
175
+ # Create a connector for the FbItemArticle updateconnector
176
+ connector = Afasgem.updateconnector_factory('FbItemArticle')
177
+ connector.insert(hash) # Insert data
178
+ connector.update(hash) # Update existing data
179
+ connector.delete(hash) # Delete existing data
180
+ ```
181
+
182
+ The methods all take a hash that represents the object. Keys in this hash are expected to be fieldnames, while their values are treated like the field's values.
183
+
184
+ ```
185
+ hash = { A: 'b', C: 5 } # => <A>b</A><C>5</C>
186
+ ```
187
+
188
+ The wrapping xml is inferred using the updateconnector name and some constrant data.
189
+
190
+ Some objects accept nested objects as well as the usual fields. Those should be stored in the Objects key of the passed hash.
191
+
192
+ ```
193
+ # <A>b</A><C>5</C>
194
+ # <Objects>
195
+ # <X>
196
+ # <Element>
197
+ # <Fields action='someaction'>
198
+ # <D>1</D>
199
+ # <E>2</E>
200
+ # </Fields>
201
+ # </Element>
202
+ # </X>
203
+ # <Y>
204
+ # <Element>
205
+ # <Fields action='someaction'>
206
+ # <F>3</F>
207
+ # <G>4</G>
208
+ # </Fields>
209
+ # </Element>
210
+ # </Y>
211
+ # </Objects>
212
+ # etc...
213
+ hash = { A: 'b', C: 5, Objects: { X: { D: 1, E: 2}, Y: { F: 3, G: 4} }
214
+ ```
215
+
216
+ ## Development
217
+
218
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
219
+
220
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
221
+
222
+ ## Contributing
223
+
224
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Bonemind/Afasgem.
225
+
226
+
227
+ ## License
228
+
229
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
230
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/afasgem.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'afasgem/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "afasgem"
8
+ spec.version = Afasgem::VERSION
9
+ spec.authors = ["Subhi Dweik"]
10
+ spec.email = ["subhime@gmail.com"]
11
+
12
+ spec.summary = %q{A gem that wraps basic afas api functionality}
13
+ spec.homepage = "https://github.com/Bonemind/Afasgem"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'savon', '>= 2.11.1'
22
+ spec.add_dependency 'httpclient'
23
+ spec.add_dependency 'rubyntlm', '~> 0.3.2'
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.10"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec"
28
+ spec.add_development_dependency 'pry'
29
+ spec.add_development_dependency 'pry-nav'
30
+ spec.add_development_dependency 'pry-stack_explorer'
31
+ spec.add_development_dependency 'pry-doc'
32
+ end
@@ -0,0 +1,177 @@
1
+ require 'savon'
2
+ require 'pp'
3
+ require 'optparse'
4
+
5
+ # Parse xml to a hash
6
+ def from_xml(xml_io)
7
+ begin
8
+ result = Nokogiri::XML(xml_io)
9
+ return { result.root.name.to_sym => xml_node_to_hash(result.root)}
10
+ rescue Exception => e
11
+ puts e.backtrace
12
+ raise
13
+ end
14
+ end
15
+
16
+ # Parse each node to a hash
17
+ def xml_node_to_hash(node)
18
+ # If we are at the root of the document, start the hash
19
+ if node.element?
20
+ result_hash = {}
21
+ if node.attributes != {}
22
+ attributes = {}
23
+ node.attributes.keys.each do |key|
24
+ attributes[node.attributes[key].name.to_sym] = node.attributes[key].value
25
+ end
26
+ end
27
+ if node.children.size > 0
28
+ node.children.each do |child|
29
+ result = xml_node_to_hash(child)
30
+
31
+ if child.name == "text"
32
+ unless child.next_sibling || child.previous_sibling
33
+ return result unless attributes
34
+ result_hash[child.name.to_sym] = result
35
+ end
36
+ elsif result_hash[child.name.to_sym]
37
+
38
+ if result_hash[child.name.to_sym].is_a?(Object::Array)
39
+ result_hash[child.name.to_sym] << result
40
+ else
41
+ result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << result
42
+ end
43
+ else
44
+ result_hash[child.name.to_sym] = result
45
+ end
46
+ end
47
+ if attributes
48
+ #add code to remove non-data attributes e.g. xml schema, namespace here
49
+ #if there is a collision then node content supersets attributes
50
+ result_hash = attributes.merge(result_hash)
51
+ end
52
+ return result_hash
53
+ else
54
+ return attributes
55
+ end
56
+ else
57
+ return node.content.to_s
58
+ end
59
+ end
60
+
61
+
62
+ # The dataconnector url, will probably not need to change
63
+ dataconnector_url = 'https://profitweb.afasonline.nl/profitservices/DataConnector.asmx?WSDL'
64
+
65
+ # The username to use
66
+ username = nil
67
+
68
+ # The password to use
69
+ password = nil
70
+
71
+ # The connector we want a description of
72
+ connector_name = nil
73
+
74
+ # The AFAS environment, probably in the format: 'X12345XX'
75
+ environment_id = nil
76
+
77
+ # Create an optionparser
78
+ OptionParser.new do |opts|
79
+ # Set username
80
+ opts.on('-u', '--username NAME', 'AFAS username') { |v| username = v }
81
+
82
+ # Set password
83
+ opts.on('-p', '--password PASSWORD', 'AFAS password') { |v| password = v }
84
+
85
+ # Set connector name
86
+ opts.on('-c', '--connector CONNECTOR', 'Name of the UpdateConnector') { |v| connector_name = v }
87
+
88
+ # Set environment
89
+ opts.on('-e', '--environment ENVIRONMENT', 'Name of the environment') { |v| environment_id = v }
90
+
91
+ # Display help
92
+ opts.on_tail("-h", "--help", "Show this message") do
93
+ puts opts
94
+ exit
95
+ end
96
+ # Parse
97
+ end.parse!
98
+
99
+ # All params are required, fail is one is missing
100
+ fail ArgumentError.new('Username is required') unless username
101
+ fail ArgumentError.new('Password is required') unless password
102
+ fail ArgumentError.new('Connector name is required') unless connector_name
103
+ fail ArgumentError.new('Environment name is required') unless environment_id
104
+
105
+ # Setup Savon with the credentials the user supplied
106
+ client = Savon.client(wsdl: dataconnector_url, basic_auth: [username, password])
107
+ resp = client.call(:execute, {
108
+ message: {
109
+ environmentId: environment_id,
110
+ userId: username,
111
+ password: password,
112
+ dataID: 'GetXmlSchema',
113
+ parametersXml: "<Parameters><UpdateConnectorId>#{connector_name}</UpdateConnectorId></Parameters>"
114
+ }
115
+ })
116
+
117
+ # Get response xml out of wrapper
118
+ xml = resp.hash[:envelope][:body][:execute_response][:execute_result]
119
+
120
+
121
+ # Get the schema xml out of the response
122
+ schemaxml = from_xml(xml)[:AfasDataConnector][:ConnectorData][:Schema]
123
+
124
+ # Parse it ignoring namespaces. This because we only need the data and will not construct responses from the data
125
+ schema = Nokogiri::XML(schemaxml).remove_namespaces!
126
+
127
+ # Xpath to get the fields array of the main object
128
+ main_object_xpath = '//*[@name="Fields" and not(ancestor::*[@name="Objects"])]'
129
+
130
+ # Xpath to get the nested objects
131
+ nested_objects_xpath = '//*[@name="Objects"]/complexType/sequence/*'
132
+
133
+ # Xpath to get the fields of the nested objects
134
+ nested_objects_fields_xpath = '*//*[@name="Fields"]/complexType/sequence'
135
+
136
+
137
+ # Array of properties of the main object, contains comment and xml element alternating
138
+ main = schema.xpath(main_object_xpath)[0].children.children.children
139
+ nested = schema.xpath(nested_objects_xpath)
140
+
141
+ # Prints the fields in a field array
142
+ def print_fields(field_array)
143
+ field_array.each do |el|
144
+ if el.is_a?(Nokogiri::XML::Comment)
145
+ puts '-' * 80
146
+ puts el
147
+ end
148
+ if el.is_a?(Nokogiri::XML::Element)
149
+ puts el.attributes['name'].value
150
+ puts 'Optional' if el.attributes['nillable']
151
+ end
152
+ end
153
+ end
154
+
155
+ # Output the main connector name
156
+ puts '*' * 8
157
+ puts connector_name
158
+ puts '*' * 80
159
+
160
+ # Output the fields of the main object
161
+ print_fields(main)
162
+
163
+ # Iterate over nested objects
164
+ nested.each do |el|
165
+
166
+ # PRint the name of the nested object
167
+ puts '=' * 80
168
+ puts el.attributes['name'].value
169
+ puts '=' * 80
170
+
171
+ # Get the fields for this object
172
+ fields = el.xpath(nested_objects_fields_xpath).children
173
+
174
+ # Print the fields for this object
175
+ print_fields(fields)
176
+ end
177
+
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "afasgem"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/lib/afasgem.rb ADDED
@@ -0,0 +1,46 @@
1
+ require "afasgem/version"
2
+ require 'afasgem/configuration'
3
+ require 'afasgem/getconnector'
4
+ require 'afasgem/updateconnector'
5
+ require 'afasgem/operators'
6
+ require 'savon'
7
+ require 'nokogiri'
8
+
9
+ module Afasgem
10
+ # Make the gem configurable
11
+ # See afasgem/configuration.rb for code origin
12
+ extend Configuration
13
+
14
+ # The WSDL url of the getconnector
15
+ define_setting :getconnector_url
16
+
17
+ # The WSDL url of the updateconnector
18
+ define_setting :updateconnector_url
19
+
20
+ # The WSDL url of the dataconnector
21
+ define_setting :dataconnector_url
22
+ # The token, this is only the actual token, not the whole xml thing
23
+ define_setting :token
24
+
25
+
26
+ # The number of results to request by default
27
+ define_setting :default_results, 100
28
+
29
+ # Defines whether the requests and responses should be printed
30
+ define_setting :debug, false
31
+
32
+ # Constructs a getconnect for the passed connector name
33
+ def self.getconnector_factory(name)
34
+ return GetConnector.new(name)
35
+ end
36
+
37
+ # Constructs an updateconnector for the passed connector name
38
+ def self.updateconnector_factory(name)
39
+ return UpdateConnector.new(name)
40
+ end
41
+
42
+ # Builds the token xml from the configured token
43
+ def self.get_token
44
+ return "<token><version>1</version><data>#{Afasgem::token}</data></token>"
45
+ end
46
+ end
@@ -0,0 +1,24 @@
1
+ # Provides module configuration
2
+ # # Source: https://viget.com/extend/easy-gem-configuration-variables-with-defaults
3
+ module Configuration
4
+ def configuration
5
+ yield self
6
+ end
7
+
8
+ def define_setting(name, default = nil)
9
+ class_variable_set("@@#{name}", default)
10
+ define_class_method "#{name}=" do |value|
11
+ class_variable_set("@@#{name}", value)
12
+ end
13
+ define_class_method name do
14
+ class_variable_get("@@#{name}")
15
+ end
16
+ end
17
+
18
+ private
19
+ def define_class_method(name, &block)
20
+ (class << self; self; end).instance_eval do
21
+ define_method name, &block
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,279 @@
1
+ # Implements communication with getconnectors
2
+ class GetConnector
3
+
4
+ # Constructor, takes the name of the connector
5
+ def initialize(name)
6
+ @connectorname = name
7
+ @filters = []
8
+ if Afasgem::debug
9
+ # Build a debug client if the debug flag is set
10
+ @client = Savon.client(
11
+ wsdl: Afasgem::getconnector_url,
12
+ log: true,
13
+ log_level: :debug,
14
+ pretty_print_xml: true
15
+ )
16
+ else
17
+ # Build a normal client otherwise
18
+ @client = Savon.client(wsdl: Afasgem::getconnector_url)
19
+ end
20
+ end
21
+
22
+ # Set the number of results we want to have
23
+ # Provides a fluent interface
24
+ def take(count)
25
+ fail ArgumentError.new("Count cannot be lower than 1, #{count} given") unless count > 0
26
+ @result = nil
27
+ @resultcount = count
28
+ return self
29
+ end
30
+
31
+ # Set the number of results we want to skip
32
+ # Provides a fluent interface
33
+ def skip(count)
34
+ fail ArgumentError.new("Count cannot be lower than 1, #{count} given") unless count > 0
35
+ @result = nil
36
+ @skip = count
37
+ return self
38
+ end
39
+
40
+ # Set the page we want to fetch
41
+ # 1 indexed (i.e. page 1 will fetch result 0 to x)
42
+ # Provides a fluent interface
43
+ def page(number)
44
+ fail ArgumentError.new("Page number cannot be lower than 1, #{number} given") unless number > 0
45
+ @result = nil
46
+ @skip = (number - 1) * get_resultcount
47
+ return self
48
+ end
49
+
50
+ # Fetch the next page
51
+ # Provides a fluent interface
52
+ def next
53
+ @skip = (@skip || 0) + get_resultcount
54
+ @result = nil
55
+ return self
56
+ end
57
+
58
+ # Fetch the previous page
59
+ # Provides a fluent interface
60
+ def previous
61
+ @result = nil
62
+ @skip = (@skip || 0) - get_resultcount
63
+ @skip = [0, @skip].max
64
+ return self
65
+ end
66
+
67
+ # execute the request
68
+ # Provides a fluent interface
69
+ def execute
70
+ result = execute_request(get_resultcount, @skip)
71
+
72
+ @data_xml = result[0]
73
+ @result = result[1]
74
+ return self
75
+ end
76
+
77
+ # Fetches all results
78
+ # Data is not cached
79
+ def get_all_results
80
+ result_array = []
81
+ skip = 0
82
+ take = 1000
83
+ loop do
84
+ current_result = get_data_from_result(execute_request(take, skip)[1])
85
+ result_array.concat(current_result)
86
+ skip = skip + take
87
+ break if current_result.size != take
88
+ end
89
+ return result_array
90
+ end
91
+
92
+ # Adds a filter to the current filter list
93
+ # Provides a fluent interface
94
+ def add_filter(field, operator, value = nil)
95
+ if @filters.size == 0
96
+ @filters.push([])
97
+ end
98
+
99
+ # Only the EMPTY and NOT_EMPTY filters should accept a nil value
100
+ if !value
101
+ unless operator == FilterOperators::EMPTY || operator == FilterOperators::NOT_EMPTY
102
+ raise ArgumentError.new('Value can only be empty when using FilterOperator::EMPTY or FilterOperator::NOT_EMPTY')
103
+ end
104
+ end
105
+ @filters.last.push({field: field, operator: operator, value: value})
106
+ return self
107
+ end
108
+
109
+ # Adds an OR to the current filter list
110
+ # Provides a fluent interface
111
+ def add_or
112
+ @filters.push([]) if @filters.last && @filters.last.size > 0
113
+ return self
114
+ end
115
+
116
+ # Clears the filters in place
117
+ # Provides a fluent interface
118
+ def clear_filters
119
+ @filters = []
120
+ return self
121
+ end
122
+
123
+ # Returns the result as a hash
124
+ # This includes the type definition
125
+ def get_result
126
+ execute unless @result
127
+ return @result
128
+ end
129
+
130
+ # Returns the actual data as a hash
131
+ def get_data
132
+ execute unless @result
133
+ return get_data_from_result(@result)
134
+ end
135
+
136
+ # Returns the raw xml
137
+ def get_data_xml
138
+ execute unless @data_xml
139
+ return @data_xml
140
+ end
141
+
142
+ private
143
+
144
+ # Actually fires the request
145
+ def execute_request(take, skip = nil)
146
+ message = {
147
+ token: Afasgem.get_token,
148
+ connectorId: @connectorname,
149
+ take: take
150
+ }
151
+
152
+ message[:skip] = skip if skip
153
+ filter_string = get_filter_string
154
+ message[:filtersXml] = filter_string if filter_string
155
+
156
+ resp = @client.call(:get_data, message: message)
157
+ xml_string = resp.hash[:envelope][:body][:get_data_response][:get_data_result]
158
+ return [xml_string, from_xml(xml_string)]
159
+ end
160
+
161
+ # Returns the filter xml in string format
162
+ def get_filter_string
163
+ return nil if @filters.size == 0
164
+ filters = []
165
+
166
+ # Loop over each filtergroup
167
+ # All conditions in a filtergroup are combined using AND
168
+ # All filtergroups are combined using OR
169
+ @filters.each_with_index do |filter, index|
170
+ fields = []
171
+
172
+ # Loop over all conditions in a filter group
173
+ filter.each do |condition|
174
+ field = condition[:field]
175
+ operator = condition[:operator]
176
+ value = condition[:value]
177
+
178
+ # Some filters operate on strings and need wildcards
179
+ # Transform value if needed
180
+ case operator
181
+ when FilterOperators::LIKE
182
+ value = "%#{value}%"
183
+ when FilterOperators::STARTS_WITH
184
+ value = "#{value}%"
185
+ when FilterOperators::NOT_LIKE
186
+ value = "%#{value}%"
187
+ when FilterOperators::NOT_STARTS_WITH
188
+ value = "#{value}%"
189
+ when FilterOperators::ENDS_WITH
190
+ value = "%#{value}"
191
+ when FilterOperators::NOT_ENDS_WITH
192
+ value = "%#{value}"
193
+ when FilterOperators::EMPTY
194
+ # EMPTY and NOT_EMPTY operators require the filter to be in a different format
195
+ # This because they take no value
196
+ fields.push("<Field FieldId=\"#{field}\" OperatorType=\"#{operator}\" />")
197
+ next
198
+ when FilterOperators::NOT_EMPTY
199
+ fields.push("<Field FieldId=\"#{field}\" OperatorType=\"#{operator}\" />")
200
+ next
201
+ end
202
+
203
+ # Add this filterstring to filters
204
+ fields.push("<Field FieldId=\"#{field}\" OperatorType=\"#{operator}\">#{value}</Field>")
205
+ end
206
+
207
+ # Make sure all filtergroups are OR'ed and add them
208
+ filters.push("<Filter FilterId=\"Filter #{index}\">#{fields.join}</Filter>")
209
+ end
210
+
211
+ # Return the whole filterstring
212
+ return "<Filters>#{filters.join}</Filters>"
213
+ end
214
+
215
+ # Returns the number of results we want to fetch
216
+ def get_resultcount
217
+ return @resultcount || Afasgem.default_results
218
+ end
219
+
220
+ # Returns the actual rows from a parsed response hash
221
+ def get_data_from_result(result)
222
+ return result[:AfasGetConnector][@connectorname.to_sym] || []
223
+ end
224
+
225
+
226
+ # Source of code below: https://gist.github.com/huy/819999
227
+ def from_xml(xml_io)
228
+ begin
229
+ result = Nokogiri::XML(xml_io)
230
+ return { result.root.name.to_sym => xml_node_to_hash(result.root)}
231
+ rescue Exception => e
232
+ # raise your custom exception here
233
+ end
234
+ end
235
+
236
+ def xml_node_to_hash(node)
237
+ # If we are at the root of the document, start the hash
238
+ if node.element?
239
+ result_hash = {}
240
+ if node.attributes != {}
241
+ attributes = {}
242
+ node.attributes.keys.each do |key|
243
+ attributes[node.attributes[key].name.to_sym] = node.attributes[key].value
244
+ end
245
+ end
246
+ if node.children.size > 0
247
+ node.children.each do |child|
248
+ result = xml_node_to_hash(child)
249
+
250
+ if child.name == "text"
251
+ unless child.next_sibling || child.previous_sibling
252
+ return result unless attributes
253
+ result_hash[child.name.to_sym] = result
254
+ end
255
+ elsif result_hash[child.name.to_sym]
256
+
257
+ if result_hash[child.name.to_sym].is_a?(Object::Array)
258
+ result_hash[child.name.to_sym] << result
259
+ else
260
+ result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << result
261
+ end
262
+ else
263
+ result_hash[child.name.to_sym] = result
264
+ end
265
+ end
266
+ if attributes
267
+ #add code to remove non-data attributes e.g. xml schema, namespace here
268
+ #if there is a collision then node content supersets attributes
269
+ result_hash = attributes.merge(result_hash)
270
+ end
271
+ return result_hash
272
+ else
273
+ return attributes
274
+ end
275
+ else
276
+ return node.content.to_s
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,17 @@
1
+ # This module maps constants to AFAS filter ints
2
+ module FilterOperators
3
+ EQUAL = 1
4
+ LARGER_OR_EQUAL = 2
5
+ SMALLER_OR_EQUAL = 3
6
+ LARGER_THAN = 4
7
+ SMALLER_THAN = 5
8
+ LIKE = 6
9
+ NOT_EQUAL = 7
10
+ EMPTY = 8
11
+ NOT_EMPTY = 9
12
+ STARTS_WITH = 10
13
+ NOT_LIKE = 11
14
+ NOT_STARTS_WITH = 12
15
+ ENDS_WITH = 13
16
+ NOT_ENDS_WITH = 14
17
+ end
@@ -0,0 +1,91 @@
1
+ class UpdateConnector
2
+
3
+ # Constructor, takes the connector name
4
+ def initialize(name)
5
+ @connectorname = name
6
+ if Afasgem::debug
7
+ # Build a debug client if the debug flag is set
8
+ @client = Savon.client(
9
+ wsdl: Afasgem::updateconnector_url,
10
+ log: true,
11
+ log_level: :debug,
12
+ pretty_print_xml: true
13
+ )
14
+ else
15
+ # Build a normal client otherwise
16
+ @client = Savon.client(wsdl: Afasgem::updateconnector_url)
17
+ end
18
+ end
19
+
20
+ # Method to return the savon client for this constructor
21
+ def client
22
+ return @client
23
+ end
24
+
25
+ # Executes an insert action using the passed object hash
26
+ def insert(objecthash)
27
+ xml = build_xml(objecthash, 'insert')
28
+ return execute(xml)
29
+ end
30
+
31
+ # Executes an update action using the passed object hash
32
+ def update(objecthash)
33
+ xml = build_xml(objecthash, 'update')
34
+ return execute(xml)
35
+ end
36
+
37
+ # Executes a delete action using the passed object hash
38
+ def delete(objecthash)
39
+ xml = build_xml(objecthash, 'delete')
40
+ return execute(xml)
41
+ end
42
+
43
+ private
44
+
45
+ # Builds the inner xml to send to the afas api from the passed hash and action
46
+ def build_xml(objecthash, action)
47
+ builder = Nokogiri::XML::Builder.new do |xml|
48
+ xml.send(@connectorname.to_sym, 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance") {
49
+ xml.Element {
50
+ xml.Fields(Action: action) {
51
+ objecthash.each do |k, v|
52
+ xml.send(k.to_sym, v) unless k.to_s == 'Objects'
53
+ build_nested_xml(xml, v, action) if k.to_s == 'Objects'
54
+ end
55
+ }
56
+ }
57
+ }
58
+ end
59
+ return builder.to_xml.to_s
60
+ end
61
+
62
+ # Builds the xml for nested objects
63
+ def build_nested_xml(xml, objects, action)
64
+ xml.Objects {
65
+ objects.each do |obj, values|
66
+
67
+ xml.send(obj) {
68
+ xml.Element {
69
+ xml.Fields(Action: action) {
70
+ values.each do |k, v|
71
+ xml.send(k, v)
72
+ end
73
+ }
74
+ }
75
+ }
76
+ end
77
+ }
78
+ end
79
+
80
+ # Actually calls the afas api
81
+ def execute(xml)
82
+ message = {
83
+ token: Afasgem.get_token,
84
+ connectorType: @connectorname,
85
+ connectorVersion: 1,
86
+ dataXml: xml
87
+ }
88
+ resp = @client.call(:execute, message: message)
89
+ return resp
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module Afasgem
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,202 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: afasgem
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Subhi Dweik
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-02-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: savon
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.11.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.11.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: httpclient
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubyntlm
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry-nav
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry-stack_explorer
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry-doc
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description:
154
+ email:
155
+ - subhime@gmail.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - ".gitignore"
161
+ - ".rspec"
162
+ - ".travis.yml"
163
+ - Gemfile
164
+ - LICENSE.txt
165
+ - README.md
166
+ - Rakefile
167
+ - afasgem.gemspec
168
+ - bin/connector_format.rb
169
+ - bin/console
170
+ - bin/setup
171
+ - lib/afasgem.rb
172
+ - lib/afasgem/configuration.rb
173
+ - lib/afasgem/getconnector.rb
174
+ - lib/afasgem/operators.rb
175
+ - lib/afasgem/updateconnector.rb
176
+ - lib/afasgem/version.rb
177
+ homepage: https://github.com/Bonemind/Afasgem
178
+ licenses:
179
+ - MIT
180
+ metadata: {}
181
+ post_install_message:
182
+ rdoc_options: []
183
+ require_paths:
184
+ - lib
185
+ required_ruby_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ required_rubygems_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ requirements: []
196
+ rubyforge_project:
197
+ rubygems_version: 2.4.8
198
+ signing_key:
199
+ specification_version: 4
200
+ summary: A gem that wraps basic afas api functionality
201
+ test_files: []
202
+ has_rdoc: