remote-resource 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8507134d807aaed5cbe3c9e7872bd1de23cb0470
4
- data.tar.gz: b074f3e0ea07f224bdfc1dc0b366722935f5c7ac
3
+ metadata.gz: 0615325b89bdbae524b7e54dc0360774ba453e55
4
+ data.tar.gz: f9441fa6d760b8f0f4e49e038498cdc0b249ffb8
5
5
  SHA512:
6
- metadata.gz: 1ce966ffea5435248135b873dfd0d8e22beb3af92d69a088562c6523de5bdd215e199daf716d397840f74f3edc19d683bb633f8f46280eab21d93b04bbcf497c
7
- data.tar.gz: bc5cd692a7271389e86c5f096a174ec0d840e1a02565d03cb5b85d296f9029f1e1dc19fca8028ef85c14cb3c24bd67c771cec1c01f19da85a098d4996cc6963a
6
+ metadata.gz: 5b73cc3191733078a6589fdda37bf1c0e45d2eb2fcd13e990f53c66ace41860dcf7a001fa078e4e70031503633c5adf9ae1a49bab721192964a3c55459820ec0
7
+ data.tar.gz: 3b8e1950c93ea03a50b95d00e0a4f75ef3faf5f2528fdfc3db55b3a87878f2169306619f011709a165bed7ada0dca83a009d7e248f0dc83a8c1312d592558bc7
data/README.md CHANGED
@@ -3,35 +3,36 @@
3
3
  [![Build Status](https://travis-ci.org/mkcode/remote-resource.svg?branch=master)](https://travis-ci.org/mkcode/remote-resource)
4
4
  [![Code Climate](https://codeclimate.com/github/mkcode/remote-resource/badges/gpa.svg)](https://codeclimate.com/github/mkcode/remote-resource)
5
5
  [![Test Coverage](https://codeclimate.com/github/mkcode/remote-resource/badges/coverage.svg)](https://codeclimate.com/github/mkcode/remote-resource/coverage)
6
- [![Inline docs](http://inch-ci.org/github/mkcode/remote_resource.svg?branch=master)](http://inch-ci.org/github/mkcode/remote_resource)
7
6
 
8
- Add resiliency, speed, and familiarity to the APIs your app relies on. Features:
7
+ __RemoteResource__ allows you to easily create `ActiveRecord` style domain
8
+ objects that represent a foreign API. These `remote resources` can be mixed into
9
+ or associated with other ActiveRecord models in the same way you work with all
10
+ your other models. Using these conventions yields some major performance gains
11
+ through caching and fast and simple development through familiarity.
9
12
 
10
- * A simple DSL for resource oriented APIs.
11
- * Work with foreign APIs in the same way you work with ActiveRecord
12
- associations.
13
- * Transparently caches API responses for major performance gains.
14
- * Don't fail when APIs your app relies on are momentarily down.
15
- * Respect your APIs Cache-Control header. Or don't. It's up to you.
16
- * Configurable logging and error reporting.
17
- * Trivial to add support for your new API client.
13
+ ## Why RemoteResource
18
14
 
19
- ## Getting started
15
+ * Familiar - The DSL used to wrap foreign APIs is simple and intuitive. Using
16
+ the remote resource will be familiar to anyone who has worked with
17
+ ActiveRecord models.
20
18
 
21
- __RemoteResource__ allows you to easily create `ActiveRecord` style domain
22
- objects (or models) that represent a foreign API. These `remote_resources` can
23
- be mixed in and associated with other ActiveRecord models in the same way you
24
- work with all your other models. Using this pattern and these conventions yields
25
- some major performance gains through caching and fast and simple development
26
- through familiarity.
19
+ * Reusable - Write your API interface once. Associate it with an ActiveRecord
20
+ object, embed it into a value object, or instantiate it for use in a service.
27
21
 
28
- A few steps to get started:
22
+ * Performant - API responses are transparently cached. Subsequent calls move at
23
+ the speed of redis. Etag based cache expiring, which you may override. Makes
24
+ detailed list pages possible.
25
+
26
+ * Resiliant - Easy to configure error handling, just like ActionContoller. Use
27
+ cached values to rescue momentary network failures.
28
+
29
+ ## Getting started
29
30
 
30
31
  Create a `remote_resource`, such as:
31
32
 
32
33
  ```ruby
33
- # in `app/remote_resources/github_user.rb`
34
- class GithubUser < HasRemote::Resource
34
+ # in app/remote_resources/github_user.rb
35
+ class GithubUser < RemoteResource::Base
35
36
  client { Octokit::Client.new }
36
37
  resource { |client, scope| client.user(scope[:github_login]) }
37
38
 
@@ -45,7 +46,7 @@ end
45
46
  Associate it with your ActiveRecord `User` model:
46
47
 
47
48
  ```ruby
48
- # in `app/models/user.rb`
49
+ # in app/models/user.rb
49
50
  class User < ActiveRecord::Base
50
51
  has_remote :github_user, scope: :github_login
51
52
 
@@ -59,7 +60,7 @@ local models.
59
60
  ```ruby
60
61
  user = User.find(1)
61
62
 
62
- user.github_user.login
63
+ user.github_user.id
63
64
  user.github_user.avatar_url
64
65
  ```
65
66
 
@@ -81,7 +82,7 @@ And then execute:
81
82
 
82
83
  Or install it yourself as:
83
84
 
84
- $ gem install remote_resource
85
+ $ gem install remote-resource
85
86
 
86
87
  ## Defining an RemoteResource
87
88
 
@@ -90,7 +91,7 @@ This folder is automatically added to your Rails eager loaded paths.
90
91
 
91
92
  ```ruby
92
93
  # In `app/remote_resources/github_user.rb
93
- class GithubUserAttributes < RemoteResource::Base
94
+ class GithubUser < RemoteResource::Base
94
95
  client { Octokit::Client.new }
95
96
 
96
97
  resource { |client, scope| client.user(scope[:github_login]) }
@@ -103,9 +104,11 @@ class GithubUserAttributes < RemoteResource::Base
103
104
  end
104
105
  ```
105
106
 
106
- The are 4 class methods that are available to help define an (API) remote resource. They are:
107
+ The are 4 class methods that are available to help define an (API) remote
108
+ resource. They are:
107
109
 
108
- * __client__: You return an instance of the web client that the API uses in a block. That block yields the scope. (More on scope later.)
110
+ * __client__: You return an instance of the web client that the API uses in a
111
+ block. That block yields the scope. (More on scope later.)
109
112
 
110
113
  * __resource__: Supply a block to the resource method that returns a a remote
111
114
  resource. For example, the 'show user' response (GET /user/:github_login)
@@ -117,10 +120,10 @@ The are 4 class methods that are available to help define an (API) remote resour
117
120
  This will be mapped to a method later. Optionally takes a second symbol
118
121
  argument referring to a non-default resource (with an argument).
119
122
 
120
- * __rescue_from__: Works in the same way that ActionContoller's rescue_from
123
+ * __rescue_from__: Works in the same way that ActionController's rescue_from
121
124
  works. It takes one or many Error class(es), and either a block of a `:with`
122
- option that refers to an instance method on this class. The block and
123
- instance method both receive the error and an additional context argument.
125
+ option that refers to an instance method on this class. The block or instance
126
+ method receive the error and an additional context hash as arguments.
124
127
 
125
128
  Remote resource allows you to define any instance method you like on it, which
126
129
  may be used by being instantiated itself or from an associated model.
@@ -144,7 +147,7 @@ The following instance methods are available within a RemoteResource::Base class
144
147
 
145
148
  ```ruby
146
149
  # In `app/remote_resources/github_user.rb
147
- class GithubUserAttributes < RemoteResource::Base
150
+ class GithubUser < RemoteResource::Base
148
151
  client { Octokit::Client.new }
149
152
  resource { |client, scope| client.user(scope[:github_login]) }
150
153
  attribute :name
@@ -200,7 +203,7 @@ github_user.markdown_summary
200
203
  #=> "<h1>A big hello to Chris Ewald!!!</h1>"
201
204
  ```
202
205
 
203
- We also may call any of our defined attributes. Ex:
206
+ We also may call any of our defined attributes.
204
207
 
205
208
  ```ruby
206
209
  github_user.name
@@ -238,19 +241,20 @@ Two methods are available for your model classes. `has_remote` and
238
241
  `embeds_remote`. They take all the same options and do mostly the same thing;
239
242
  create a method on the calling object, which returns that records associated
240
243
  RemoteResource instance. `embeds_remote` will go one step further and define all
241
- of the attribute getter methods on the model class as well. This can be used to
242
- create 'flat' domain objects which are backed by values from a remote API. This
243
- is largely related to Inhertance vs Composition programming theory which you are
244
- welcome to look up on your own time. RemoteResource supports both styles; 'Is'
245
- through `embeds_remote` and 'has' through `has_remote`. If unsure, you should
246
- prefer to use `has_remote` over `embeds_remote` to create a clear distinction
247
- between your local and remote domain.
244
+ of the attribute getter methods on the calling class as well. This can be used
245
+ to create flat domain objects, or possibly value_objects, which are backed by
246
+ values from a remote API. This is largely related to Inhertance vs Composition
247
+ in programming theory which you are welcome to look up on your own time.
248
+ RemoteResource supports both styles; 'Is' through `embeds_remote` and 'has'
249
+ through `has_remote`. If unsure, it is best to prefer composition and use
250
+ `has_remote` over `embeds_remote` to create a clear distinction between your
251
+ local and remote domain.
248
252
 
249
253
  ## Extending other domain objects
250
254
 
251
- If you do not use ActiveRecord in your app, you may still use remote_resource by
252
- simply extending the Bridge module onto what class you use as your domain. The
253
- `has_remote` and `embed_remote` methods will then be available. For example:
255
+ If you do not use ActiveRecord in your app, you may still use remote-resource by
256
+ simply extending the Bridge module onto whatever class you use. The `has_remote`
257
+ and `embed_remote` methods will then be available. For example:
254
258
 
255
259
  ```ruby
256
260
  class MyPoro
@@ -261,54 +265,69 @@ end
261
265
 
262
266
  ## Configuration
263
267
 
264
- In a initializer, like `config/initializers/remote_resource.rb`, you may override the following options:
268
+ In a initializer, like `config/initializers/remote_resource.rb`, you may
269
+ override the following options:
265
270
 
266
271
  ```ruby
267
- # Setup global storages. For now there is only redis and memory. Default is one
268
- # Memory store.
269
-
270
- require 'remote_resource/storage/redis'
272
+ # Setup global storages. For now there are Redis and Memory stores available.
273
+ # Default is Memory store.
274
+
275
+ # Storage::Redis takes an instance of redis client and the following options:
276
+ #
277
+ # expires_in - Time in seconds for to keys ttl. (Default is 1 day)
278
+ # serializer - Instance of the serializer to load and dump the response.
279
+ # (Default is MarshalSerializer.new)
280
+ #
271
281
  RemoteResource.storages = [
272
- RemoteResource::Storage::Redis.new( Redis.new(url:nil) )
282
+ RemoteResource::Storage::Redis.new(Redis.new(url:nil), expires_in: 7.days)
273
283
  ]
274
284
 
275
- # Setup a logger
285
+ # Specify the logger RemoteResource should use:
276
286
 
277
287
  RemoteResource.logger = Logger.new(STDOUT)
278
288
 
279
- # Setup a lookup method. Only default for now, but the `cache_control` option
289
+ # Setup a lookup method. Only default for now, but the `validate` option
280
290
  # may be changed to true or false. True will always revalidate. False will never
281
291
  # revalidate. :cache_control respects the Cache-Control header.
282
292
 
283
- require 'remote_resource/lookup/default'
284
293
  RemoteResource.lookup_method = RemoteResource::Lookup::Default.new(validate: true)
285
294
  ```
286
295
 
287
296
  ## Notifications
288
297
 
289
- There are 4 ActiveSupport notifications that you may subscribe to, to do in depth profiling of this gem:
298
+ There are 3 ActiveSupport notifications that you may subscribe to, to do in
299
+ depth profiling of this gem:
290
300
 
291
301
  * find.remote_resource
292
302
  * storage_lookup.remote_resource
293
- * http_head.remote_resource
294
303
  * http_get.remote_resource
295
304
 
305
+ ```ruby
296
306
  ActiveSupport::Notifications.subscribe('http_get.remote_resource') do |name, _start, _fin, _id, _payload|
297
307
  puts "HTTP_GET #{name}"
298
308
  end
309
+ ```
299
310
 
300
311
  ## Development
301
312
 
302
- 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.
313
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
314
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
315
+ prompt that will allow you to experiment.
303
316
 
304
- 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).
317
+ To install this gem onto your local machine, run `bundle exec rake install`. To
318
+ release a new version, update the version number in `version.rb`, and then run
319
+ `bundle exec rake release`, which will create a git tag for the version, push
320
+ git commits and tags, and push the `.gem` file to
321
+ [rubygems.org](https://rubygems.org).
305
322
 
306
323
  ## Contributing
307
324
 
308
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/remote_resource. 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.
309
-
325
+ Bug reports and pull requests are welcome on GitHub at
326
+ https://github.com/[USERNAME]/remote_resource. This project is intended to be a
327
+ safe, welcoming space for collaboration, and contributors are expected to adhere
328
+ to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
310
329
 
311
330
  ## License
312
331
 
313
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
314
-
332
+ The gem is available as open source under the terms of the
333
+ [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,4 @@
1
+ # This file exists as shortcut; so that Bundler.require will properly require
2
+ # this gem's project files without additional configuration.
3
+
4
+ require 'remote_resource'
@@ -29,6 +29,7 @@ module RemoteResource
29
29
 
30
30
  def define_association_method(method_name, target_class)
31
31
  scope = @options[:scope]
32
+ scope = ":#{@options[:scope]}" if @options[:scope].is_a?(Symbol)
32
33
  target_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
33
34
  def #{method_name}
34
35
  scope_evaluator = RemoteResource::ScopeEvaluator.new(#{scope})
@@ -1,12 +1,18 @@
1
- require 'remote_resource/storage/serializers/marshal'
1
+ require 'active_support/core_ext/hash/reverse_merge'
2
+
3
+ require 'remote_resource/storage/serializers/marshal_serializer'
2
4
  require 'remote_resource/storage/storage_entry'
3
5
 
4
6
  module RemoteResource
5
7
  module Storage
6
8
  class Redis
7
- def initialize(redis, serializer = nil)
9
+ def initialize(redis, options = {})
8
10
  @redis = redis
9
- @serializer = serializer || Serializers::MarshalSerializer.new
11
+ @options = options.reverse_merge(
12
+ serializer: Serializers::MarshalSerializer.new,
13
+ expires_in: 1 * (60 * 60 * 24)
14
+ )
15
+ @serializer = @options[:serializer]
10
16
  end
11
17
 
12
18
  def read_key(key)
@@ -20,7 +26,10 @@ module RemoteResource
20
26
  storage_entry.to_hash.each_pair do |key, value|
21
27
  write_args.concat([key, @serializer.dump(value)]) unless value.empty?
22
28
  end
23
- @redis.hmset storage_key, *write_args
29
+ @redis.multi do |multi|
30
+ multi.hmset storage_key, *write_args
31
+ multi.expire storage_key, @options[:expires_in]
32
+ end
24
33
  end
25
34
  end
26
35
  end
@@ -1,4 +1,4 @@
1
- require_relative '../serializer'
1
+ require 'remote_resource/storage/serializer'
2
2
 
3
3
  module RemoteResource
4
4
  module Storage
@@ -1,3 +1,3 @@
1
1
  module RemoteResource
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
@@ -31,4 +31,30 @@ module RemoteResource
31
31
  autoload :Bridge
32
32
  autoload :LogSubscriber
33
33
  autoload :ScopeEvaluator
34
+
35
+ module Lookup
36
+ extend ActiveSupport::Autoload
37
+
38
+ autoload :Default
39
+ end
40
+
41
+ autoload_under 'storage' do
42
+ autoload :CacheControl
43
+ autoload :NullStorageEntry
44
+ autoload :StorageEntry
45
+ end
46
+
47
+ module Storage
48
+ extend ActiveSupport::Autoload
49
+
50
+ autoload :Memory
51
+ autoload :Redis
52
+ autoload :Serializer
53
+
54
+ module Serializers
55
+ extend ActiveSupport::Autoload
56
+
57
+ autoload :MarshalSerializer
58
+ end
59
+ end
34
60
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: remote-resource
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Ewald
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-25 00:00:00.000000000 Z
11
+ date: 2016-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -114,6 +114,7 @@ files:
114
114
  - Rakefile
115
115
  - bin/console
116
116
  - bin/setup
117
+ - lib/remote-resource.rb
117
118
  - lib/remote_resource.rb
118
119
  - lib/remote_resource/association_builder.rb
119
120
  - lib/remote_resource/attribute_http_client.rb
@@ -137,13 +138,11 @@ files:
137
138
  - lib/remote_resource/railtie.rb
138
139
  - lib/remote_resource/scope_evaluator.rb
139
140
  - lib/remote_resource/storage/cache_control.rb
140
- - lib/remote_resource/storage/db_cache.rb
141
- - lib/remote_resource/storage/db_cache_factory.rb
142
141
  - lib/remote_resource/storage/memory.rb
143
142
  - lib/remote_resource/storage/null_storage_entry.rb
144
143
  - lib/remote_resource/storage/redis.rb
145
144
  - lib/remote_resource/storage/serializer.rb
146
- - lib/remote_resource/storage/serializers/marshal.rb
145
+ - lib/remote_resource/storage/serializers/marshal_serializer.rb
147
146
  - lib/remote_resource/storage/storage_entry.rb
148
147
  - lib/remote_resource/version.rb
149
148
  - remote-resource.gemspec
@@ -1,36 +0,0 @@
1
- require 'remote_resource/storage/serializers/marshal'
2
-
3
- module RemoteResource
4
- class DBCache
5
- ADAPTERS = %i(active_record)
6
-
7
- attr_reader :column_name
8
- attr_accessor :target_instance
9
-
10
- def initialize(_adapter, column_name, _serializer = :marshal)
11
- @adapter = :active_record
12
- @column_name = column_name.to_sym
13
- @serializer = Serializers::MarshalSerializer.new
14
- end
15
-
16
- # always returns a hash
17
- def read_column
18
- raw = @target_instance.read_attribute(column_name)
19
- raw ? @serializer.load(raw) : {}
20
- end
21
-
22
- def write_column(hash)
23
- fail ArgumentError 'must be a hash!' unless hash.is_a? Hash
24
- raw = @serializer.dump(hash)
25
- @target_instance.update_attribute(column_name, raw)
26
- end
27
-
28
- def read_key(key)
29
- read_column[key.to_sym]
30
- end
31
-
32
- def write_key(key, value)
33
- write_column(read_column.merge(key.to_sym => value))
34
- end
35
- end
36
- end
@@ -1,38 +0,0 @@
1
- require 'remote_resource/storage/db_cache'
2
-
3
- module RemoteResource
4
- class UnsupportedDatabase < StandardError; end
5
-
6
- class DBCacheFactory
7
- def initialize(base_class, options)
8
- @base_class = base_class
9
- @options = options
10
- end
11
-
12
- def create_for_class(target_class)
13
- db_cache_adapter = db_cache_adapter_for(target_class)
14
- if db_cache_adapter
15
- column_name = @options[:cache_column] ||
16
- default_or_magic_column_name_for(@base_class)
17
- DBCache.new(db_cache_adapter, column_name)
18
- else
19
- fail UnsupportedDatabase
20
- end
21
- end
22
-
23
- private
24
-
25
- def default_or_magic_column_name_for(base_class)
26
- "#{base_class.underscore}_cache"
27
- end
28
-
29
- def db_cache_adapter_for(klass)
30
- DBCache::ADAPTERS.detect do |adapter_name|
31
- klass.ancestors.any? do |parent_class|
32
- next unless (class_name = parent_class.name)
33
- class_name.split('::').first.underscore.to_sym == adapter_name
34
- end
35
- end
36
- end
37
- end
38
- end