integrative 0.1.0
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/README.md +191 -0
- data/Rakefile +34 -0
- data/lib/integrative.rb +35 -0
- data/lib/integrative/errors.rb +76 -0
- data/lib/integrative/extensions.rb +7 -0
- data/lib/integrative/extensions/relation_extension.rb +37 -0
- data/lib/integrative/integrated.rb +62 -0
- data/lib/integrative/integration.rb +52 -0
- data/lib/integrative/integrator.rb +66 -0
- data/lib/integrative/utils.rb +10 -0
- data/lib/integrative/version.rb +3 -0
- data/lib/tasks/integrative_tasks.rake +4 -0
- metadata +100 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: a841fed65f619a6170d26d8816ff8f697d39c463
|
|
4
|
+
data.tar.gz: 906a568cf159349a97f435714a0989f1c1dc36db
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 034c141465f37b82b4b4449f97cef1ced395a367d52fe9109d172e9c36a199f3baf068d618c527722cbcb8b32aded0b2e916ade2f511a509d4ab30368d9c6c74
|
|
7
|
+
data.tar.gz: 77c8c554abe0adcb2edfb661677c7e8200e78734c365256c1dc1d324437ff8d8a4641f2cf3045fa59a3d1164a2b82e19cf627d30cb2d561c885fb9cefaae3e99
|
data/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Integrative
|
|
2
|
+
|
|
3
|
+
Integrative is a library for integrating external resources into ActiveRecord models.
|
|
4
|
+
|
|
5
|
+
Now, however you interpret "external" - this library is exactly for that ;-)
|
|
6
|
+
|
|
7
|
+
Cosider few exaples of what can be integrated into ActiveRecord model:
|
|
8
|
+
* ActiveResource model
|
|
9
|
+
* a custom object that fetches data from external websites
|
|
10
|
+
* an object fetching data from Redis
|
|
11
|
+
* another ActiveRecord model
|
|
12
|
+
|
|
13
|
+
You may ask
|
|
14
|
+
|
|
15
|
+
> :triumph:: ok, but why would I use Integrative? I can easily implement that on my own.
|
|
16
|
+
|
|
17
|
+
> :sunglasses:: I'm glad you asked. The best reason is that **it helps to fetch a lot of data at once**, and by that it significantly improves performance.
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
### Example 1: Another data store
|
|
21
|
+
|
|
22
|
+
Imagine the following context:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
class User < ApplicationRecord
|
|
26
|
+
include Integrative::Integrator
|
|
27
|
+
|
|
28
|
+
integrates :user_flag
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class UserFlag < SomeRedisObject
|
|
32
|
+
include Integrative::Integrated
|
|
33
|
+
|
|
34
|
+
attr_accessor :user_id
|
|
35
|
+
attr_accessor :name
|
|
36
|
+
|
|
37
|
+
def self.find(ids)
|
|
38
|
+
# Have in mind it's a simplification.
|
|
39
|
+
# `find` should return array of hashes
|
|
40
|
+
# with (in this case) `name` and `user_id`
|
|
41
|
+
# so you'd need to store hashes
|
|
42
|
+
# and convert data accordingly
|
|
43
|
+
@redis.mget(*ids)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Now let's say you would like to see the list of all users with their flags. Try this:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
users = User.limit(1000).integrate(:user_flag).to_a
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**the above code will call redis only once** and will fetch user_flag for all 1000 users,
|
|
55
|
+
so now you can access all the flags like this:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
users.map { |user| user.user_flag.name }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Example 2: Prefetching another Active Record model
|
|
62
|
+
|
|
63
|
+
You can use Integrative also when you want to eager-load certain models to collection of other models when `ActiveRecord` doesn't make it easy.
|
|
64
|
+
Let's say you have the following situation:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
class User < ApplicationRecord
|
|
68
|
+
include Integrative::Integrator
|
|
69
|
+
|
|
70
|
+
integrates :relation, requires: [:with]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class Relation
|
|
74
|
+
include Integrative::Integrated
|
|
75
|
+
|
|
76
|
+
def self.integrative_find(ids, integration)
|
|
77
|
+
Relation.where(user_id: integration.call_options[:with].id, other_user_id: ids)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
Now you want to fetch some Users and have already prefatched information about their relation with the current user.
|
|
82
|
+
|
|
83
|
+
With `Integrative` you just do:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
User.where(public: true).integrate(:relation, with: current_user).limit(1000)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Boom. Pretty cool, ha?
|
|
90
|
+
|
|
91
|
+
## Treating integrated object as primary type value (string, int, ...)
|
|
92
|
+
|
|
93
|
+
Now check this out:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
class User < ApplicationRecord
|
|
97
|
+
integrates :is_admin, as: :primary
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
User.integrate(:is_admin).first.is_admin # that would be `true` or `false`
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Of course for that you'd need to take care for preparing data properly in the integrated object:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
class IsAdmin
|
|
107
|
+
include Integrative::Integrated
|
|
108
|
+
|
|
109
|
+
def self.integrative_find(ids, integration)
|
|
110
|
+
# this should return a list of hashes
|
|
111
|
+
# with a key (e.g. user_id) and a `value`,
|
|
112
|
+
# for example:
|
|
113
|
+
# [
|
|
114
|
+
# {user_id: 1, value: true}
|
|
115
|
+
# {user_id: 2, value: false}
|
|
116
|
+
# ]
|
|
117
|
+
response = find(ids)
|
|
118
|
+
response.map { |item| OpenStruct.new(item) }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Integrating objects with `1-to-many` relation
|
|
124
|
+
|
|
125
|
+
Like with `has_one` and `has_many` relations, sometimes you want to assign one external object
|
|
126
|
+
per model, but sometimes you want to assign an array of external objects per model. In such moments use `array: true` as an option parameter of integration
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
class User < ApplicationRecord
|
|
130
|
+
integrates :flags, array: true
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
User.first.flags # this is an array
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Using `Integrative` on a single instance
|
|
137
|
+
|
|
138
|
+
So what if you'd like to prefetch something not for a list of users, but for a single user?
|
|
139
|
+
Well, it works exactly how you would think:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
user = User.first
|
|
143
|
+
user.flags # yes, that's gonna fetch and return a list of flags of the user.
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Using `Integrative` on an array
|
|
147
|
+
|
|
148
|
+
Sometimes you just want to prefetch certain data for an array of objects (and not for `ActiveRecord::Relation`). In such case just do:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
users_with_flags = Integrative.integrate_into(users, :user_flags)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Working with external resources
|
|
155
|
+
|
|
156
|
+
While working with external resources you need to implement the code that fetches external data and then assigns parts of it to the right models. Now it's all up to you how you'll do this but there is a pattern that fits well into `Integrative`. Take a look:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
# file app/models/integrative_record.rb
|
|
160
|
+
class IntegrativeRecord
|
|
161
|
+
include Integrative::Integrated
|
|
162
|
+
|
|
163
|
+
def url_base
|
|
164
|
+
'http://external.service.com'
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def full_url(ids)
|
|
168
|
+
url_base + path(ids)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# file app/models/avatar.rb
|
|
173
|
+
class Avatar < IntegrativeRecord
|
|
174
|
+
|
|
175
|
+
def path(ids)
|
|
176
|
+
"avatars?user_ids=#{ids.join(',')}"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def find(ids)
|
|
180
|
+
response = RestClient.get full_path(ids)
|
|
181
|
+
response_hash = HashWithIndifferentAccess.new(JSON.parse(response.body))
|
|
182
|
+
response_hash[:results]
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Contributing
|
|
188
|
+
|
|
189
|
+
If you feel like contributing to this project, feel free to create a bug report or send a pull request, but if you want to increase chances that I'll find time for taking care for your contribution, please make sure to make it easy for me - for pull requests write tests, for bug reports attach code that will let me reproduce the issue.
|
|
190
|
+
|
|
191
|
+
Have fun ;-)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
rescue LoadError
|
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require 'rdoc/task'
|
|
8
|
+
|
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
11
|
+
rdoc.title = 'Integrative'
|
|
12
|
+
rdoc.options << '--line-numbers'
|
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
require 'bundler/gem_tasks'
|
|
23
|
+
|
|
24
|
+
require 'rake/testtask'
|
|
25
|
+
|
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
|
27
|
+
t.libs << 'lib'
|
|
28
|
+
t.libs << 'test'
|
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
|
30
|
+
t.verbose = false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
task default: :test
|
data/lib/integrative.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Integrative lets you add objects to ActiveRecord relation or to an array
|
|
2
|
+
#
|
|
3
|
+
# class User < ApplicationRecord
|
|
4
|
+
# integrates :relation, require: [:with]
|
|
5
|
+
# end
|
|
6
|
+
#
|
|
7
|
+
# class Relation
|
|
8
|
+
# include Integrative::Integrated
|
|
9
|
+
#
|
|
10
|
+
# attr_accessor :user_id
|
|
11
|
+
# attr_accessor :kind
|
|
12
|
+
#
|
|
13
|
+
# ...
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# Now let's say you would like to have the list of all users with their relations.
|
|
17
|
+
# Try this:
|
|
18
|
+
#
|
|
19
|
+
# users = User.limit(1000).integrate(:relation, with: current_user).to_a
|
|
20
|
+
#
|
|
21
|
+
# and the integration of relations will happen for the whole collection and not
|
|
22
|
+
# just for individual users.
|
|
23
|
+
require 'integrative/utils'
|
|
24
|
+
|
|
25
|
+
module Integrative
|
|
26
|
+
extend Utils
|
|
27
|
+
|
|
28
|
+
autoload :Integrator, 'integrative/integrator'
|
|
29
|
+
autoload :Integrated, 'integrative/integrated'
|
|
30
|
+
autoload :Integration, 'integrative/integration'
|
|
31
|
+
autoload :Errors, 'integrative/errors'
|
|
32
|
+
autoload :Extensions, 'integrative/extensions'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Integrative
|
|
2
|
+
module Errors
|
|
3
|
+
class IntegrationError < StandardError
|
|
4
|
+
attr_accessor :integration
|
|
5
|
+
|
|
6
|
+
def initialize(message, integration)
|
|
7
|
+
@integration = integration
|
|
8
|
+
super(message)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class RuntimeOptionMissingError < IntegrationError
|
|
13
|
+
def initialize(integration)
|
|
14
|
+
message = "You used 'integrate' for #{integration.name} without options," +
|
|
15
|
+
" but the following options are required: " +
|
|
16
|
+
" #{integration.init_options[:requires]}"
|
|
17
|
+
super(message, integration)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class UnexpectedRuntimeOptionError < IntegrationError
|
|
22
|
+
def initialize(integration)
|
|
23
|
+
required = integration.call_options.keys
|
|
24
|
+
message = "You used 'integrate' for #{integration.name} with unexpected options," +
|
|
25
|
+
" you should define integration like this:" +
|
|
26
|
+
" 'integrates :#{integration.name}, requires: [:#{required.join(", :")}]'"
|
|
27
|
+
super(message, integration)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class TooManyRuntimeOptionsError < IntegrationError
|
|
32
|
+
def initialize(integration, unexpected_options)
|
|
33
|
+
message = "You used 'integrate' for :#{integration.name}" +
|
|
34
|
+
" on #{integration.integrator_class.name}" +
|
|
35
|
+
" with too many options: #{unexpected_options}"
|
|
36
|
+
super(message, integration)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class TooLittleRuntimeOptionsError < IntegrationError
|
|
41
|
+
def initialize(integration, missing_options)
|
|
42
|
+
message = "You used 'integrate' for :#{integration.name}" +
|
|
43
|
+
" on #{integration.integrator_class.name}" +
|
|
44
|
+
" with too little options: #{missing_options}"
|
|
45
|
+
super(message, integration)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class IntegratorError < StandardError
|
|
50
|
+
attr_accessor :integration
|
|
51
|
+
|
|
52
|
+
def initialize(message, integrator)
|
|
53
|
+
@integrator = integrator
|
|
54
|
+
super(message)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class MethodAlreadyExistsError < IntegratorError
|
|
59
|
+
def initialize(integrator, name)
|
|
60
|
+
message = "Method '#{name}' is already defined on #{integrator.name}." +
|
|
61
|
+
" You can not define integration with this name."
|
|
62
|
+
super(message, integrator)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class IntegrationDefinitionMissingError < IntegratorError
|
|
67
|
+
def initialize(integrator, names)
|
|
68
|
+
message = "You tried to call `integrate` on a class #{integrator.name}" +
|
|
69
|
+
" but this class doesn't have this integration." +
|
|
70
|
+
" add the following line to the class #{integrator.name}:" +
|
|
71
|
+
" 'integrates :#{names.join(', :')}'"
|
|
72
|
+
super(message, integrator)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Integrative
|
|
2
|
+
module Extensions
|
|
3
|
+
module RelationExtension
|
|
4
|
+
def integrate(*name_or_names, **options)
|
|
5
|
+
names = [*name_or_names]
|
|
6
|
+
names.each do |name|
|
|
7
|
+
integrate_per_name(name, options)
|
|
8
|
+
end
|
|
9
|
+
self
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def load
|
|
13
|
+
super
|
|
14
|
+
if @integrations_used.present?
|
|
15
|
+
Rails.logger.info "Integrations fetched for #{@records.length} #{klass.name} records."
|
|
16
|
+
@integrations_used.each do |integration|
|
|
17
|
+
integration.integrated_class.integrative_find_and_assign(@records, integration)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def integrate_per_name(name, options)
|
|
26
|
+
integration = klass.integrations_defined.find { |i| i.name == name }
|
|
27
|
+
if integration.nil?
|
|
28
|
+
raise Errors::IntegrationDefinitionMissingError.new(klass, [name])
|
|
29
|
+
end
|
|
30
|
+
integration.call_options = options
|
|
31
|
+
integration.invalidate
|
|
32
|
+
@integrations_used ||= []
|
|
33
|
+
@integrations_used << integration
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'ostruct'
|
|
2
|
+
|
|
3
|
+
module Integrative
|
|
4
|
+
module Integrated
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
def integrative_find(ids, options)
|
|
9
|
+
find(ids)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def integrator_ids(integrator_records, integration)
|
|
13
|
+
integrator_records.map(&integration.integrator_key)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def integrative_find_and_assign(integrator_records, integration)
|
|
17
|
+
ids = integrator_ids(integrator_records, integration)
|
|
18
|
+
integrated = integrative_find(ids, integration)
|
|
19
|
+
integrated_by_integrator_id = array_to_hash(integrated, integration)
|
|
20
|
+
integrator_records.each do |record|
|
|
21
|
+
record.public_send(integration.setter, integrated_by_integrator_id[record.id])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def integrative_value(object, integration)
|
|
26
|
+
if [:primary, :value, :simple].include? integration.init_options[:as]
|
|
27
|
+
object[:value]
|
|
28
|
+
else
|
|
29
|
+
object
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def array_to_hash(array, integration)
|
|
36
|
+
if integration.init_options[:array]
|
|
37
|
+
array_to_hash_as_array(array, integration)
|
|
38
|
+
else
|
|
39
|
+
array_to_hash_as_value(array, integration)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def array_to_hash_as_value(array, integration)
|
|
44
|
+
result = array.map do |object|
|
|
45
|
+
key = object.public_send(integration.integrated_key)
|
|
46
|
+
[key, integrative_value(object, integration)]
|
|
47
|
+
end
|
|
48
|
+
Hash[result]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def array_to_hash_as_array(array, integration)
|
|
52
|
+
result = {}
|
|
53
|
+
array.each do |object|
|
|
54
|
+
key = object.public_send(integration.integrated_key)
|
|
55
|
+
result[key] ||= []
|
|
56
|
+
result[key] << integrative_value(object, integration)
|
|
57
|
+
end
|
|
58
|
+
result
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Integrative
|
|
2
|
+
class Integration
|
|
3
|
+
attr_accessor :name
|
|
4
|
+
attr_accessor :integrator_class
|
|
5
|
+
attr_accessor :integrated_class
|
|
6
|
+
attr_accessor :init_options
|
|
7
|
+
attr_accessor :call_options
|
|
8
|
+
|
|
9
|
+
def initialize(name, integrator_class, options)
|
|
10
|
+
@name = name
|
|
11
|
+
@integrator_class = integrator_class
|
|
12
|
+
@integrated_class = name.to_s.camelize.singularize.constantize
|
|
13
|
+
@init_options = options
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def invalidate
|
|
17
|
+
if call_options.blank? && init_options[:requires].present?
|
|
18
|
+
raise Errors::RuntimeOptionMissingError.new(self)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if call_options.present? && init_options[:requires].blank?
|
|
22
|
+
raise Errors::UnexpectedRuntimeOptionError.new(self)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if call_options.present? && init_options[:requires].present?
|
|
26
|
+
unexpected_options = call_options.keys - init_options[:requires]
|
|
27
|
+
missing_options = init_options[:requires] - call_options.keys
|
|
28
|
+
|
|
29
|
+
if unexpected_options.present?
|
|
30
|
+
raise Errors::TooManyRuntimeOptionsError.new(self, unexpected_options)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if missing_options.present?
|
|
34
|
+
raise Errors::TooLittleRuntimeOptionsError.new(self, missing_options)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def setter
|
|
40
|
+
"#{name}="
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def integrator_key
|
|
44
|
+
init_options[:integrator_key] || :id
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def integrated_key
|
|
48
|
+
default_integrated_key = "#{integrator_class.name.underscore}_id"
|
|
49
|
+
init_options[:integrated_key] || default_integrated_key
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Integrative
|
|
2
|
+
module Integrator
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
def integrative_dynamic_method_call(name, integration)
|
|
6
|
+
ivar = "@#{name}"
|
|
7
|
+
if instance_variable_defined? ivar
|
|
8
|
+
instance_variable_get ivar
|
|
9
|
+
else
|
|
10
|
+
Rails.logger.info "Integrations fetched for a single #{self.class.name} record."
|
|
11
|
+
integration.integrated_class.integrative_find_and_assign([self], integration)
|
|
12
|
+
instance_variable_get ivar
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class_methods do
|
|
17
|
+
def integrates(name, options = {})
|
|
18
|
+
initialize_integrative(name)
|
|
19
|
+
define_integration(name, options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def integrate(*name_or_names, **options)
|
|
23
|
+
if all.public_methods.include? :integrate
|
|
24
|
+
all.integrate(*name_or_names, **options)
|
|
25
|
+
else
|
|
26
|
+
raise Errors::IntegrationDefinitionMissingError.new(self, name_or_names)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def patch_activerecord_relation_for_integrative
|
|
31
|
+
self::ActiveRecord_Relation.class_eval do
|
|
32
|
+
include Integrative::Extensions::RelationExtension
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def initialize_integrative(name)
|
|
39
|
+
if self.instance_methods.include? name
|
|
40
|
+
raise Errors::MethodAlreadyExistsError.new(self, name)
|
|
41
|
+
end
|
|
42
|
+
if !defined?(integrations_defined)
|
|
43
|
+
patch_activerecord_relation_for_integrative
|
|
44
|
+
class_attribute :integrations_defined
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def define_integration(name, options)
|
|
49
|
+
integration = Integration.new(name, self, options)
|
|
50
|
+
self.integrations_defined ||= []
|
|
51
|
+
self.integrations_defined << integration
|
|
52
|
+
self.class_eval do
|
|
53
|
+
define_integration_method(name, integration)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def define_integration_method(name, integration)
|
|
58
|
+
attr_accessor name
|
|
59
|
+
|
|
60
|
+
define_method name do
|
|
61
|
+
integrative_dynamic_method_call(name, integration)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Integrative
|
|
2
|
+
module Utils
|
|
3
|
+
def integrate_into(records, integration_name, options = {})
|
|
4
|
+
if records.length > 0
|
|
5
|
+
integration = Integration.new(integration_name, records.first.class, options)
|
|
6
|
+
integration.integrated_class.integrative_find_and_assign(records, integration)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: integrative
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Krzysiek Herod
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-07-12 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 5.0.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 5.0.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: sqlite3
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
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: factory_girl
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
description: Integrative is a library for integrating external resources into ActiveRecord
|
|
56
|
+
models.
|
|
57
|
+
email:
|
|
58
|
+
- krzysiek.herod@gmail.com
|
|
59
|
+
executables: []
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- README.md
|
|
64
|
+
- Rakefile
|
|
65
|
+
- lib/integrative.rb
|
|
66
|
+
- lib/integrative/errors.rb
|
|
67
|
+
- lib/integrative/extensions.rb
|
|
68
|
+
- lib/integrative/extensions/relation_extension.rb
|
|
69
|
+
- lib/integrative/integrated.rb
|
|
70
|
+
- lib/integrative/integration.rb
|
|
71
|
+
- lib/integrative/integrator.rb
|
|
72
|
+
- lib/integrative/utils.rb
|
|
73
|
+
- lib/integrative/version.rb
|
|
74
|
+
- lib/tasks/integrative_tasks.rake
|
|
75
|
+
homepage: https://github.com/netizer/integrative
|
|
76
|
+
licenses:
|
|
77
|
+
- MIT
|
|
78
|
+
metadata: {}
|
|
79
|
+
post_install_message:
|
|
80
|
+
rdoc_options: []
|
|
81
|
+
require_paths:
|
|
82
|
+
- lib
|
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '0'
|
|
93
|
+
requirements: []
|
|
94
|
+
rubyforge_project:
|
|
95
|
+
rubygems_version: 2.6.6
|
|
96
|
+
signing_key:
|
|
97
|
+
specification_version: 4
|
|
98
|
+
summary: Integrative is a library for integrating external resources into ActiveRecord
|
|
99
|
+
models.
|
|
100
|
+
test_files: []
|