seiun 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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/auth_credentials.example.yml +9 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/seiun.rb +25 -0
- data/lib/seiun/callback/extends.rb +27 -0
- data/lib/seiun/callback/record_wrapper.rb +31 -0
- data/lib/seiun/callback/wrapper.rb +81 -0
- data/lib/seiun/client.rb +86 -0
- data/lib/seiun/connection.rb +111 -0
- data/lib/seiun/job.rb +104 -0
- data/lib/seiun/queue.rb +36 -0
- data/lib/seiun/utils.rb +21 -0
- data/lib/seiun/version.rb +3 -0
- data/lib/seiun/xml_generators/base.rb +24 -0
- data/lib/seiun/xml_generators/batch_xml.rb +55 -0
- data/lib/seiun/xml_generators/job_xml.rb +43 -0
- data/lib/seiun/xml_parsers/base.rb +41 -0
- data/lib/seiun/xml_parsers/batch_xml.rb +17 -0
- data/lib/seiun/xml_parsers/job_xml.rb +19 -0
- data/lib/seiun/xml_parsers/record_xml.rb +18 -0
- data/lib/seiun/xml_parsers/result_xml.rb +33 -0
- data/lib/seiun/xml_parsers/stream_listener.rb +42 -0
- data/seiun.gemspec +27 -0
- metadata +132 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: f5007ec6a09aa9bafcfb5ca763bf3f79dd84a2ba
|
|
4
|
+
data.tar.gz: 770f1d6e68f386243d032fdd31e86ea789dc8dbb
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a569196984d50e6345426fc477d099e2274e31334f5e206d079da235e097a15bdf5544a7258f1b08800f101888d68f6a4293ceae8071523cb6c8d93926eaf3c9
|
|
7
|
+
data.tar.gz: b83f143bd8923414748e2959943da767da60b93c653fa6e45352b352f1deaca1457537e935347ac8e4a0a08c97bfae077566c97ce914d013d0bdbf94a29d26b7
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.3.0
|
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Contributor Code of Conduct
|
|
2
|
+
|
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
|
7
|
+
|
|
8
|
+
We are committed to making participation in this project a harassment-free
|
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
|
12
|
+
|
|
13
|
+
Examples of unacceptable behavior by participants include:
|
|
14
|
+
|
|
15
|
+
* The use of sexualized language or imagery
|
|
16
|
+
* Personal attacks
|
|
17
|
+
* Trolling or insulting/derogatory comments
|
|
18
|
+
* Public or private harassment
|
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
|
20
|
+
addresses, without explicit permission
|
|
21
|
+
* Other unethical or unprofessional conduct
|
|
22
|
+
|
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
|
27
|
+
threatening, offensive, or harmful.
|
|
28
|
+
|
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
|
32
|
+
Conduct may be permanently removed from the project team.
|
|
33
|
+
|
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
|
35
|
+
when an individual is representing the project or its community.
|
|
36
|
+
|
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
38
|
+
reported by contacting a project maintainer at naoki.watanabe@ikina.org. All
|
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
|
42
|
+
incident.
|
|
43
|
+
|
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
45
|
+
version 1.3.0, available at
|
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
|
47
|
+
|
|
48
|
+
[homepage]: http://contributor-covenant.org
|
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Naoki Watanabe
|
|
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,41 @@
|
|
|
1
|
+
# Seiun
|
|
2
|
+
|
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/seiun`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
4
|
+
|
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'seiun'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
$ bundle
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
$ gem install seiun
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
TODO: Write usage instructions here
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
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.
|
|
30
|
+
|
|
31
|
+
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).
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/seiun. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
41
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# To exec rspec and connect salesforce.com
|
|
2
|
+
# Enter your account below
|
|
3
|
+
|
|
4
|
+
salesforce:
|
|
5
|
+
username: yourname@email.com
|
|
6
|
+
password: your_password
|
|
7
|
+
security_token: your_security_token
|
|
8
|
+
customer_key: customer_key_on_this_application
|
|
9
|
+
consumer_secret: customer_secret_on_this_application
|
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "seiun"
|
|
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
data/lib/seiun.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'rexml/document'
|
|
2
|
+
require 'rexml/parsers/baseparser'
|
|
3
|
+
require 'rexml/parsers/streamparser'
|
|
4
|
+
require 'rexml/streamlistener'
|
|
5
|
+
require "seiun/version"
|
|
6
|
+
require "seiun/utils"
|
|
7
|
+
require "seiun/xml_generators/base"
|
|
8
|
+
require "seiun/xml_generators/batch_xml"
|
|
9
|
+
require "seiun/xml_generators/job_xml"
|
|
10
|
+
require "seiun/xml_parsers/base"
|
|
11
|
+
require "seiun/xml_parsers/batch_xml"
|
|
12
|
+
require "seiun/xml_parsers/job_xml"
|
|
13
|
+
require "seiun/xml_parsers/record_xml"
|
|
14
|
+
require "seiun/xml_parsers/result_xml"
|
|
15
|
+
require "seiun/xml_parsers/stream_listener"
|
|
16
|
+
require "seiun/callback/extends"
|
|
17
|
+
require "seiun/callback/record_wrapper"
|
|
18
|
+
require "seiun/callback/wrapper"
|
|
19
|
+
require "seiun/connection"
|
|
20
|
+
require "seiun/job"
|
|
21
|
+
require "seiun/queue"
|
|
22
|
+
require "seiun/client"
|
|
23
|
+
|
|
24
|
+
module Seiun
|
|
25
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module Callback
|
|
3
|
+
module Extends
|
|
4
|
+
module ClassMethods
|
|
5
|
+
[ :hashalize, :after_build_xml, :before_request, :after_response, :ssl_verify_none,
|
|
6
|
+
:mock_response_create_job, :mock_response_close_job, :mock_response_add_query, :mock_response_add_batch,
|
|
7
|
+
:mock_response_get_job_details, :mock_response_get_batch_details, :mock_response_get_result, :mock_response_get_query_result
|
|
8
|
+
].each do |callback_point|
|
|
9
|
+
define_method "seiun_#{callback_point}" do |method_name|
|
|
10
|
+
@seiun_callbacks ||= {}
|
|
11
|
+
@seiun_callbacks[callback_point] = method_name
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def seiun_callbacks
|
|
16
|
+
@seiun_callbacks
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
extend ClassMethods
|
|
21
|
+
|
|
22
|
+
def self.included(klass)
|
|
23
|
+
klass.extend ClassMethods
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module Callback
|
|
3
|
+
class RecordWrapper
|
|
4
|
+
def initialize(record)
|
|
5
|
+
@record = record
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def to_hash
|
|
9
|
+
if callback_defined?(:hashalize)
|
|
10
|
+
@record.__send__(:hashalize)
|
|
11
|
+
elsif @record.respond_to?(:to_hash)
|
|
12
|
+
@record.to_hash
|
|
13
|
+
else
|
|
14
|
+
@record
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def callback_defined?(name)
|
|
21
|
+
!!callbacks[name]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def callbacks
|
|
25
|
+
return {} unless @record.class.respond_to?(:seiun_callbacks)
|
|
26
|
+
return {} unless @record.class.seiun_callbacks.is_a?(Hash)
|
|
27
|
+
@record.class.seiun_callbacks
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module Callback
|
|
3
|
+
class Wrapper
|
|
4
|
+
def initialize(callback_class)
|
|
5
|
+
@class = callback_class
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def after_build_xml(xml)
|
|
9
|
+
return unless callback_defined?(:after_build_xml)
|
|
10
|
+
@class.__send__(callbacks[:after_build_xml], xml)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def before_request(request_type, path, request_body)
|
|
14
|
+
return unless callback_defined?(:before_request)
|
|
15
|
+
@class.__send__(callbacks[:before_request], request_type, path, request_body)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def after_response(request_type, path, response_body)
|
|
19
|
+
return unless callback_defined?(:after_response)
|
|
20
|
+
@class.__send__(callbacks[:after_response], request_type, path, response_body)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ssl_verify_none
|
|
24
|
+
return unless callback_defined?(:ssl_verify_none)
|
|
25
|
+
@class.__send__(callbacks[:ssl_verify_none])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def mock_response_create_job
|
|
29
|
+
return unless callback_defined?(:mock_response_create_job)
|
|
30
|
+
@class.__send__(callbacks[:mock_response_create_job])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def mock_response_close_job
|
|
34
|
+
return unless callback_defined?(:mock_response_close_job)
|
|
35
|
+
@class.__send__(callbacks[:mock_response_close_job])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def mock_response_add_query
|
|
39
|
+
return unless callback_defined?(:mock_response_add_query)
|
|
40
|
+
@class.__send__(callbacks[:mock_response_add_query])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def mock_response_add_batch
|
|
44
|
+
return unless callback_defined?(:mock_response_add_batch)
|
|
45
|
+
@class.__send__(callbacks[:mock_response_add_batch])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def mock_response_get_job_details
|
|
49
|
+
return unless callback_defined?(:mock_response_get_job_details)
|
|
50
|
+
@class.__send__(callbacks[:mock_response_get_job_details])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def mock_response_get_batch_details
|
|
54
|
+
return unless callback_defined?(:mock_response_get_batch_details)
|
|
55
|
+
@class.__send__(callbacks[:mock_response_get_batch_details])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def mock_response_get_result
|
|
59
|
+
return unless callback_defined?(:mock_response_get_result)
|
|
60
|
+
@class.__send__(callbacks[:mock_response_get_result])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def mock_response_get_query_result
|
|
64
|
+
return unless callback_defined?(:mock_response_get_query_result)
|
|
65
|
+
@class.__send__(callbacks[:mock_response_get_query_result])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def callback_defined?(name)
|
|
71
|
+
!!callbacks[name]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def callbacks
|
|
75
|
+
return {} unless @class.respond_to?(:seiun_callbacks)
|
|
76
|
+
return {} unless @class.seiun_callbacks.is_a?(Hash)
|
|
77
|
+
@class.seiun_callbacks
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/seiun/client.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
class Client
|
|
3
|
+
SEC_TO_WAIT_ASYNC = 180
|
|
4
|
+
private_constant :SEC_TO_WAIT_ASYNC
|
|
5
|
+
|
|
6
|
+
def initialize(databasedotcom: nil, batch_size: 10_000)
|
|
7
|
+
@batch_size = batch_size
|
|
8
|
+
@connection = Seiun::Connection.new(databasedotcom)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def insert(table_name, records, callback_class: nil, async: true)
|
|
12
|
+
operate(:insert, table_name, records: records, callback: callback_class, async: async)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def insert_queue(table_name, callback_class: nil, async: true)
|
|
16
|
+
@insert_queue ||= {}
|
|
17
|
+
@insert_queue[table_name] ||= Seiun::Queue.new(batch_size: @batch_size) do |records|
|
|
18
|
+
insert(table_name, records, callback_class: callback_class, async: async)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update(table_name, records, callback_class: nil, async: true)
|
|
23
|
+
operate(:update, table_name, records: records, callback: callback_class, async: async)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update_queue(table_name, callback_class: nil, async: true)
|
|
27
|
+
@update_queue ||= {}
|
|
28
|
+
@update_queue[table_name] ||= Seiun::Queue.new(batch_size: @batch_size) do |records|
|
|
29
|
+
update(table_name, records, callback_class: callback_class, async: async)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def upsert(table_name, records, ext_field_name, callback_class: nil, async: true)
|
|
34
|
+
operate(:upsert, table_name, records: records,
|
|
35
|
+
ext_field_name: ext_field_name, callback: callback_class, async: async)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def upsert_queue(table_name, ext_field_name, callback_class: nil, async: true)
|
|
39
|
+
@upsert_queue ||= {}
|
|
40
|
+
@upsert_queue[table_name] ||= Seiun::Queue.new(batch_size: @batch_size) do |records|
|
|
41
|
+
upsert(table_name, records, ext_field_name, callback_class: callback_class, async: async)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def delete(table_name, records, callback_class: nil, async: true)
|
|
46
|
+
operate(:delete, table_name, records: records, callback: callback_class, async: async)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def delete_queue(table_name, callback_class: nil, async: true)
|
|
50
|
+
@delete_queue ||= {}
|
|
51
|
+
@delete_queue[table_name] ||= Seiun::Queue.new(batch_size: @batch_size) do |records|
|
|
52
|
+
delete(table_name, records, callback_class: callback_class, async: async)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def query(table_name, soql, callback_class: nil)
|
|
57
|
+
operate(:query, table_name, soql: soql, callback: callback_class)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def operate(operation, object, records: [], soql: "", ext_field_name: nil, callback: nil, async: true)
|
|
63
|
+
callback = Seiun::Callback::Wrapper.new(callback) if callback
|
|
64
|
+
records = records.map{|r| Seiun::Callback::RecordWrapper.new(r) }
|
|
65
|
+
job = Seiun::Job.new(@connection, operation, object, ext_field_name: ext_field_name, callback: callback)
|
|
66
|
+
job.post_creation
|
|
67
|
+
operation == :query ? job.add_query(soql) : records.each_slice(@batch_size).each{|chunk| job.add_batch(chunk) }
|
|
68
|
+
job.post_closing
|
|
69
|
+
wait_asyc(job) if operation == :query || async == false
|
|
70
|
+
operation == :query ? job.get_query_result : job
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def wait_asyc(job)
|
|
74
|
+
Timeout.timeout(sec_to_wait_async) do
|
|
75
|
+
until job.all_batch_finished?
|
|
76
|
+
job.get_batch_details
|
|
77
|
+
sleep 1
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def sec_to_wait_async
|
|
83
|
+
SEC_TO_WAIT_ASYNC
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
class Connection
|
|
3
|
+
def initialize(databasedotcom)
|
|
4
|
+
@databasedotcom = databasedotcom
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def create_job(data, callback: nil)
|
|
8
|
+
connect(:create_job, data: data, callback: callback)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def close_job(data, job_id, callback: nil)
|
|
12
|
+
connect(:close_job, data: data, job_id: job_id, callback: callback)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_query(soql, job_id, callback: nil)
|
|
16
|
+
connect(:add_query, data: soql, job_id: job_id, callback: callback)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_batch(data, job_id, callback: nil)
|
|
20
|
+
connect(:add_batch, data: data, job_id: job_id, callback: callback)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get_batch_details(job_id, callback: nil)
|
|
24
|
+
connect(:get_batch_details, job_id: job_id, callback: callback)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def get_result(job_id, batch_id, callback: nil)
|
|
28
|
+
connect(:get_result, job_id: job_id, batch_id: batch_id, callback: callback)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get_query_result(job_id, batch_id, result_id, callback: nil)
|
|
32
|
+
connect(:get_query_result, job_id: job_id, batch_id: batch_id, result_id: result_id, callback: callback)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def connect(type, data: nil, job_id: nil, batch_id: nil, result_id: nil, callback: nil)
|
|
38
|
+
path = request_path(type, job_id, batch_id, result_id)
|
|
39
|
+
callback.before_request(type, path.dup, data) if callback
|
|
40
|
+
if callback && mock_body = callback.__send__("mock_response_#{type}")
|
|
41
|
+
body = mock_body
|
|
42
|
+
else
|
|
43
|
+
response = nil
|
|
44
|
+
raise_over_retry 3 do
|
|
45
|
+
response = request(type, path, data, job_id, batch_id, result_id, callback: callback)
|
|
46
|
+
response.value
|
|
47
|
+
end
|
|
48
|
+
body = response.body
|
|
49
|
+
end
|
|
50
|
+
callback.after_response(type, path, body) if callback
|
|
51
|
+
body
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def request(type, path, data, job_id, batch_id, result_id, callback: nil)
|
|
55
|
+
https = Net::HTTP.new(instance_host, 443)
|
|
56
|
+
https.use_ssl = true
|
|
57
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_NONE if callback && callback.ssl_verify_none
|
|
58
|
+
case type
|
|
59
|
+
when :create_job, :close_job, :add_query, :add_batch
|
|
60
|
+
https.post(path, data, headers)
|
|
61
|
+
else
|
|
62
|
+
https.get(path, headers)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def request_path(type, job_id, batch_id, result_id)
|
|
67
|
+
case type
|
|
68
|
+
when :create_job
|
|
69
|
+
"/services/async/#{api_version}/job"
|
|
70
|
+
when :close_job
|
|
71
|
+
"/services/async/#{api_version}/job/#{job_id}"
|
|
72
|
+
when :add_query, :add_batch, :get_batch_details
|
|
73
|
+
"/services/async/#{api_version}/job/#{job_id}/batch"
|
|
74
|
+
when :get_result
|
|
75
|
+
"/services/async/#{api_version}/job/#{job_id}/batch/#{batch_id}/result"
|
|
76
|
+
when :get_query_result
|
|
77
|
+
"/services/async/#{api_version}/job/#{job_id}/batch/#{batch_id}/result/#{result_id}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def instance_host
|
|
82
|
+
return @instance_host if @instance_host
|
|
83
|
+
@databasedotcom.instance_url =~ /^https*\:\/\/([^\/]+)/
|
|
84
|
+
@instance_host = $1
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def headers
|
|
88
|
+
{ "X-SFDC-Session" => session_id, 'Content-Type' => 'application/xml; charset=UTF-8' }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def session_id
|
|
92
|
+
@session_id ||= @databasedotcom.oauth_token
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def api_version
|
|
96
|
+
"35.0"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def raise_over_retry(times)
|
|
100
|
+
count = 0
|
|
101
|
+
begin
|
|
102
|
+
yield
|
|
103
|
+
rescue => ex
|
|
104
|
+
count += 1
|
|
105
|
+
raise ex, ex.response.body if count >= times
|
|
106
|
+
sleep 1
|
|
107
|
+
retry
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/seiun/job.rb
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
class Job
|
|
3
|
+
attr_reader :operation
|
|
4
|
+
|
|
5
|
+
def initialize(connection, operation, object_name, id: nil, ext_field_name: nil, callback: nil)
|
|
6
|
+
@connection = connection
|
|
7
|
+
@operation = operation
|
|
8
|
+
@object_name = object_name
|
|
9
|
+
@id = id if id
|
|
10
|
+
@ext_field_name = ext_field_name if ext_field_name
|
|
11
|
+
@callback = callback
|
|
12
|
+
@batches = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def post_creation
|
|
16
|
+
response_body = @connection.create_job(create_job_xml, callback: @callback)
|
|
17
|
+
parse_job_xml(response_body)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def post_closing
|
|
21
|
+
response_body = @connection.close_job(close_job_xml, @id, callback: @callback)
|
|
22
|
+
parse_job_xml(response_body)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def add_query(soql)
|
|
26
|
+
response_body = @connection.add_query(soql, @id, callback: @callback)
|
|
27
|
+
parse_batch_xml(response_body)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_batch(records)
|
|
31
|
+
response_body = @connection.add_batch(add_batch_xml(records), @id, callback: @callback)
|
|
32
|
+
parse_batch_xml(response_body)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def get_batch_details
|
|
36
|
+
response_body = @connection.get_batch_details(@id, callback: @callback)
|
|
37
|
+
parse_batch_xml(response_body)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_query_result
|
|
41
|
+
result = []
|
|
42
|
+
@batches.each do |batch|
|
|
43
|
+
result_response_body = @connection.get_result(@id, batch.id, callback: @callback)
|
|
44
|
+
Seiun::XMLParsers::ResultXML.each(result_response_body) do |result_response|
|
|
45
|
+
response_body = @connection.get_query_result(@id, batch.id, result_response.result_id, callback: @callback)
|
|
46
|
+
Seiun::XMLParsers::RecordXML.each(response_body) do |response|
|
|
47
|
+
result << response.to_hash
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
result
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def all_batch_finished?
|
|
55
|
+
raise "Batches are empty" if @batches.empty?
|
|
56
|
+
@batches.all?{|batch| !["Queued", "InProgress"].include?(batch.sf_state) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def create_job_xml
|
|
62
|
+
Seiun::XMLGenerators::JobXML.create_job(@operation, @object_name, ext_field_name: @ext_field_name, callback: @callback)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def close_job_xml
|
|
66
|
+
Seiun::XMLGenerators::JobXML.close_job(callback: @callback)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def add_batch_xml(records)
|
|
70
|
+
Seiun::XMLGenerators::BatchXML.add_batch(records, callback: @callback)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parse_job_xml(response_body)
|
|
74
|
+
Seiun::XMLParsers::JobXML.each(response_body) do |response|
|
|
75
|
+
@id = response.id || @id
|
|
76
|
+
@sf_created_at = response.created_date || @sf_created_at
|
|
77
|
+
@sf_updated_at = response.system_modstamp || @sf_updated_at
|
|
78
|
+
@sf_state = response.state || @sf_state
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def parse_batch_xml(response_body)
|
|
83
|
+
Seiun::XMLParsers::BatchXML.each(response_body) do |response|
|
|
84
|
+
unless batch = @batches.find{|batch| batch.id == response.id }
|
|
85
|
+
batch = Batch.new(response.id)
|
|
86
|
+
@batches << batch
|
|
87
|
+
end
|
|
88
|
+
batch.job_id = response.job_id || batch.job_id
|
|
89
|
+
batch.sf_state = response.state || batch.sf_state
|
|
90
|
+
batch.sf_created_at = response.created_date || batch.sf_created_at
|
|
91
|
+
batch.sf_updated_at = response.system_modstamp || batch.sf_updated_at
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class Batch
|
|
96
|
+
attr_reader :id
|
|
97
|
+
attr_accessor :job_id, :sf_state, :sf_created_at, :sf_updated_at, :number_records_processed
|
|
98
|
+
|
|
99
|
+
def initialize(id)
|
|
100
|
+
@id = id
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
data/lib/seiun/queue.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
class Queue
|
|
3
|
+
def initialize(batch_size: 10_000, &operation)
|
|
4
|
+
@batch_size = batch_size
|
|
5
|
+
@operation = operation
|
|
6
|
+
initialize_queue
|
|
7
|
+
@jobs = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def <<(record)
|
|
11
|
+
push(record)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def push(record)
|
|
15
|
+
@queue << record
|
|
16
|
+
operate if @queue.size == @batch_size
|
|
17
|
+
record
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def close
|
|
21
|
+
operate
|
|
22
|
+
@jobs
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def operate
|
|
28
|
+
@jobs << @operation.call(@queue)
|
|
29
|
+
initialize_queue
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize_queue
|
|
33
|
+
@queue = []
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/seiun/utils.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
class Utils
|
|
3
|
+
class << self
|
|
4
|
+
def camelize(str)
|
|
5
|
+
str.to_s.gsub(/_([a-z])/){|match| $1.upcase }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def underscore(str)
|
|
9
|
+
str.to_s.gsub(/([a-z0-9])([A-Z])/){|match| "#{$1}_#{$2.downcase}" }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def parsable_date?(str)
|
|
13
|
+
str.to_s =~ /^[1-4][0-9]{3}-(?:0[1-9]|1[012])-(?:0[1-9]|[12][0-9]|3[01])$/
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parsable_time?(str)
|
|
17
|
+
str.to_s =~ /^[1-4][0-9]{3}-(?:0[1-9]|1[012])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]/
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module XMLGenerators
|
|
3
|
+
class Base
|
|
4
|
+
def initialize(callback: nil)
|
|
5
|
+
@callback = callback
|
|
6
|
+
@rexml_doc = REXML::Document.new
|
|
7
|
+
@rexml_doc << REXML::XMLDecl.new('1.0', 'UTF-8')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_s
|
|
11
|
+
io = StringIO.new
|
|
12
|
+
rexml_doc.write(io)
|
|
13
|
+
io.rewind
|
|
14
|
+
io.read
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def rexml_doc
|
|
20
|
+
@rexml_doc
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module XMLGenerators
|
|
3
|
+
class BatchXML < Base
|
|
4
|
+
class << self
|
|
5
|
+
def add_batch(records, callback: nil)
|
|
6
|
+
generator = new(callback: callback)
|
|
7
|
+
generator.add_batch(records)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_batch(records)
|
|
12
|
+
sobjects = rexml_doc.add_element("sObjects",
|
|
13
|
+
"xmlns" => "http://www.force.com/2009/06/asyncapi/dataload",
|
|
14
|
+
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance")
|
|
15
|
+
records.each do |record|
|
|
16
|
+
add_record(sobjects, record)
|
|
17
|
+
end
|
|
18
|
+
to_s
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
@callback.after_build_xml(rexml_doc) if @callback
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def add_record(parent, record)
|
|
29
|
+
sobject = parent.add_element("sObject")
|
|
30
|
+
record.to_hash.each_pair do |key, value|
|
|
31
|
+
if value.is_a?(Hash)
|
|
32
|
+
relation = sobject.add_element(key.to_s)
|
|
33
|
+
add_record(relation, value)
|
|
34
|
+
elsif value.is_a?(NilClass) || value.is_a?(String) && value.empty?
|
|
35
|
+
sobject.add_element(key.to_s, "xsi:nil" => "true")
|
|
36
|
+
else
|
|
37
|
+
sobject.add_element(key.to_s).add_text(convert_value(value))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def convert_value(value)
|
|
43
|
+
if value.is_a?(Time) || value.is_a?(DateTime)
|
|
44
|
+
value.iso8601.to_s
|
|
45
|
+
elsif value.is_a?(Date)
|
|
46
|
+
value.strftime("%Y-%m-%d")
|
|
47
|
+
elsif value.is_a?(Array)
|
|
48
|
+
value.join(";")
|
|
49
|
+
else
|
|
50
|
+
value.to_s
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module XMLGenerators
|
|
3
|
+
class JobXML < Base
|
|
4
|
+
class << self
|
|
5
|
+
def create_job(operation, object, ext_field_name: nil, callback: nil)
|
|
6
|
+
generator = new(callback: callback)
|
|
7
|
+
generator.create_job(operation, object, ext_field_name: ext_field_name)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def close_job(callback: nil)
|
|
11
|
+
generator = new(callback: callback)
|
|
12
|
+
generator.close_job
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create_job(operation, object, ext_field_name: nil)
|
|
17
|
+
create_job_info do |jobinfo|
|
|
18
|
+
jobinfo.add_element("operation").add_text(operation.to_s)
|
|
19
|
+
jobinfo.add_element("object").add_text(object.to_s)
|
|
20
|
+
jobinfo.add_element("externalIdFieldName").add_text(ext_field_name.to_s) if ext_field_name
|
|
21
|
+
jobinfo.add_element("contentType").add_text("XML")
|
|
22
|
+
end
|
|
23
|
+
to_s
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def close_job
|
|
27
|
+
create_job_info do |jobinfo|
|
|
28
|
+
jobinfo.add_element("state").add_text("Closed")
|
|
29
|
+
end
|
|
30
|
+
to_s
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def create_job_info
|
|
36
|
+
jobinfo = rexml_doc.add_element("jobInfo",
|
|
37
|
+
"xmlns" => "http://www.force.com/2009/06/asyncapi/dataload",
|
|
38
|
+
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance")
|
|
39
|
+
yield(jobinfo)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module XMLParsers
|
|
3
|
+
class Base
|
|
4
|
+
class << self
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def parse(xml_str, find_tag, block)
|
|
8
|
+
callback = Proc.new {|attrs| block.call(new(attrs)) }
|
|
9
|
+
listener = XMLParsers::StreamListener.new(find_tag, callback)
|
|
10
|
+
REXML::Parsers::StreamParser.new(xml_str, listener).parse
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(attrs)
|
|
15
|
+
@attrs = attrs
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_hash(strict_mode = false)
|
|
19
|
+
return {} unless @attrs.is_a?(Array)
|
|
20
|
+
@attrs.each_with_object({}) do |attribute, result|
|
|
21
|
+
key, values = attribute.to_a.first
|
|
22
|
+
results = [values].flatten.map{|value|
|
|
23
|
+
next if value.is_a?(Hash) && value["xsi:nil"] == "true"
|
|
24
|
+
next value if strict_mode
|
|
25
|
+
if Seiun::Utils.parsable_date?(value) && ( date = Date.parse(value, false) rescue nil )
|
|
26
|
+
next date
|
|
27
|
+
end
|
|
28
|
+
if Seiun::Utils.parsable_time?(value) && ( time = Time.iso8601(value) rescue nil )
|
|
29
|
+
next time
|
|
30
|
+
end
|
|
31
|
+
next true if value == "true"
|
|
32
|
+
next false if value == "false"
|
|
33
|
+
value
|
|
34
|
+
}
|
|
35
|
+
results.uniq! unless strict_mode
|
|
36
|
+
result[key] = ( results.size <= 1 ? results.first : results )
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module XMLParsers
|
|
3
|
+
class BatchXML < Base
|
|
4
|
+
class << self
|
|
5
|
+
def each(xml_str, &block)
|
|
6
|
+
parse(xml_str, "batchInfo", block)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
[ :id, :job_id, :state, :created_date, :system_modstamp ].each do |name|
|
|
11
|
+
define_method name do
|
|
12
|
+
to_hash(true)[Seiun::Utils.camelize(name)]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module XMLParsers
|
|
3
|
+
class JobXML < Base
|
|
4
|
+
class << self
|
|
5
|
+
def each(xml_str, &block)
|
|
6
|
+
parse(xml_str, "jobInfo", block)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
[ :id, :operation, :object, :created_by_id, :created_date, :system_modstamp,
|
|
11
|
+
:state, :content_type, :external_id_field_name
|
|
12
|
+
].each do |name|
|
|
13
|
+
define_method name do
|
|
14
|
+
to_hash(true)[Seiun::Utils.camelize(name)]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module XMLParsers
|
|
3
|
+
class RecordXML < Base
|
|
4
|
+
class << self
|
|
5
|
+
def each(xml_str, find_tag: "records", &block)
|
|
6
|
+
parse(xml_str, find_tag, block)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_hash(strict_mode = false)
|
|
11
|
+
source = super
|
|
12
|
+
source.keys.find_all{|key| key =~ /(^[A-Z]|__c$)/ }.each_with_object({}) do |key, hash|
|
|
13
|
+
hash[key] = source[key]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module XMLParsers
|
|
3
|
+
class ResultXML < Base
|
|
4
|
+
class << self
|
|
5
|
+
def each(xml_str, &block)
|
|
6
|
+
parse(xml_str, "result", block)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def result_id
|
|
11
|
+
return unless result.is_a?(String)
|
|
12
|
+
result
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
[ :id, :success ].each do |name|
|
|
16
|
+
define_method name do
|
|
17
|
+
return unless result.is_a?(Hash)
|
|
18
|
+
result[Seiun::Utils.camelize(name)]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def result
|
|
25
|
+
if @attrs.size == 1 && @attrs.first.is_a?(String)
|
|
26
|
+
@attrs.first
|
|
27
|
+
else
|
|
28
|
+
to_hash
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Seiun
|
|
2
|
+
module XMLParsers
|
|
3
|
+
class StreamListener
|
|
4
|
+
include REXML::StreamListener
|
|
5
|
+
|
|
6
|
+
def initialize(find_tag, callback)
|
|
7
|
+
@find_tag = find_tag
|
|
8
|
+
@callback = callback
|
|
9
|
+
@stack = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def tag_start(name, attrs)
|
|
13
|
+
if @stack.empty? && name == @find_tag
|
|
14
|
+
element = []
|
|
15
|
+
element << attrs unless attrs.empty?
|
|
16
|
+
@current = element
|
|
17
|
+
@stack = [ element ]
|
|
18
|
+
elsif @current
|
|
19
|
+
element = []
|
|
20
|
+
element << attrs unless attrs.empty?
|
|
21
|
+
@stack.last << { name => element }
|
|
22
|
+
@stack.push(element)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def text(text)
|
|
27
|
+
text = text.strip
|
|
28
|
+
return if text.empty?
|
|
29
|
+
@stack.last << text if @current
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def tag_end(name)
|
|
33
|
+
if @stack.size == 1 && name == @find_tag
|
|
34
|
+
@callback.call(@current)
|
|
35
|
+
@current, @stack = nil, []
|
|
36
|
+
elsif @current
|
|
37
|
+
pop_tag = @stack.pop
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/seiun.gemspec
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'seiun/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "seiun"
|
|
8
|
+
spec.version = Seiun::VERSION
|
|
9
|
+
spec.authors = ["Naoki Watanabe"]
|
|
10
|
+
spec.email = ["naoki.watanabe@ikina.org"]
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{Salesforce Adapter for Ruby environment.}
|
|
13
|
+
spec.description = %q{You can communicate Salesforce via Bulk API from ruby environment.}
|
|
14
|
+
spec.homepage = "https://github.com/naoki-watanabe/seiun"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
|
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
18
|
+
spec.bindir = "exe"
|
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
20
|
+
spec.require_paths = ["lib"]
|
|
21
|
+
|
|
22
|
+
spec.add_dependency 'databasedotcom', '>= 1.0.8'
|
|
23
|
+
|
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: seiun
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Naoki Watanabe
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-12-31 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: databasedotcom
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 1.0.8
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 1.0.8
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: bundler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.11'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.11'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '10.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '10.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.0'
|
|
69
|
+
description: You can communicate Salesforce via Bulk API from ruby environment.
|
|
70
|
+
email:
|
|
71
|
+
- naoki.watanabe@ikina.org
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- ".gitignore"
|
|
77
|
+
- ".rspec"
|
|
78
|
+
- ".ruby-version"
|
|
79
|
+
- ".travis.yml"
|
|
80
|
+
- CODE_OF_CONDUCT.md
|
|
81
|
+
- Gemfile
|
|
82
|
+
- LICENSE.txt
|
|
83
|
+
- README.md
|
|
84
|
+
- Rakefile
|
|
85
|
+
- auth_credentials.example.yml
|
|
86
|
+
- bin/console
|
|
87
|
+
- bin/setup
|
|
88
|
+
- lib/seiun.rb
|
|
89
|
+
- lib/seiun/callback/extends.rb
|
|
90
|
+
- lib/seiun/callback/record_wrapper.rb
|
|
91
|
+
- lib/seiun/callback/wrapper.rb
|
|
92
|
+
- lib/seiun/client.rb
|
|
93
|
+
- lib/seiun/connection.rb
|
|
94
|
+
- lib/seiun/job.rb
|
|
95
|
+
- lib/seiun/queue.rb
|
|
96
|
+
- lib/seiun/utils.rb
|
|
97
|
+
- lib/seiun/version.rb
|
|
98
|
+
- lib/seiun/xml_generators/base.rb
|
|
99
|
+
- lib/seiun/xml_generators/batch_xml.rb
|
|
100
|
+
- lib/seiun/xml_generators/job_xml.rb
|
|
101
|
+
- lib/seiun/xml_parsers/base.rb
|
|
102
|
+
- lib/seiun/xml_parsers/batch_xml.rb
|
|
103
|
+
- lib/seiun/xml_parsers/job_xml.rb
|
|
104
|
+
- lib/seiun/xml_parsers/record_xml.rb
|
|
105
|
+
- lib/seiun/xml_parsers/result_xml.rb
|
|
106
|
+
- lib/seiun/xml_parsers/stream_listener.rb
|
|
107
|
+
- seiun.gemspec
|
|
108
|
+
homepage: https://github.com/naoki-watanabe/seiun
|
|
109
|
+
licenses:
|
|
110
|
+
- MIT
|
|
111
|
+
metadata: {}
|
|
112
|
+
post_install_message:
|
|
113
|
+
rdoc_options: []
|
|
114
|
+
require_paths:
|
|
115
|
+
- lib
|
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
117
|
+
requirements:
|
|
118
|
+
- - ">="
|
|
119
|
+
- !ruby/object:Gem::Version
|
|
120
|
+
version: '0'
|
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
|
+
requirements:
|
|
123
|
+
- - ">="
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: '0'
|
|
126
|
+
requirements: []
|
|
127
|
+
rubyforge_project:
|
|
128
|
+
rubygems_version: 2.5.1
|
|
129
|
+
signing_key:
|
|
130
|
+
specification_version: 4
|
|
131
|
+
summary: Salesforce Adapter for Ruby environment.
|
|
132
|
+
test_files: []
|