afasgem 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: