pinky 0.1.4-java → 0.2.5-java

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.
data/Rakefile CHANGED
@@ -15,7 +15,10 @@ require 'jeweler'
15
15
  Jeweler::Tasks.new do |gem|
16
16
  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
17
  gem.name = "pinky"
18
+
19
+ # FIXME: jf (12.12.12) removing due to bug in Bundler
18
20
  gem.platform = 'java'
21
+
19
22
  gem.homepage = "http://github.com/trunkclub/pinky"
20
23
  gem.license = "MIT"
21
24
  gem.summary = %Q{in memory API caching made easy}
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.4
1
+ 0.2.5
@@ -5,11 +5,9 @@ module Pinky
5
5
  find_by = opts[:lookup_by] || "#{klass_name}_id"
6
6
  association_name = opts[:as] || klass_name
7
7
  define_method association_name do
8
- begin
9
- klass.find send(find_by)
10
- rescue NotFoundException
11
- raise unless opts[:allow_nil]
12
- end
8
+ lookup_value = send(find_by)
9
+ association_model = lookup_value.nil? ? nil : klass.find(:id => lookup_value) rescue nil
10
+ raise NotFoundException.new if association_model.nil? && !opts[:allow_nil]
13
11
  end
14
12
  end
15
13
  end
@@ -0,0 +1,50 @@
1
+ module Pinky
2
+ class Cache
3
+ extend Forwardable
4
+
5
+ attr_reader :name
6
+ def_delegators :cache, :size, :count, :empty?
7
+
8
+ class << self
9
+ def name_for(*methods)
10
+ "cache_by_#{methods.flatten.sort.join("_and_")}"
11
+ end
12
+ end
13
+
14
+ def initialize(*item_methods)
15
+ @item_methods = item_methods.flatten.sort
16
+ @name = self.class.name_for(@item_methods)
17
+ end
18
+
19
+ def queryable_for?(query_hash)
20
+ query_hash.keys.sort == @item_methods
21
+ end
22
+
23
+ def update_with(item, action = :create)
24
+ key = key_for item
25
+ if action == :destroy
26
+ cache.delete key
27
+ else
28
+ cache[key] = item
29
+ end
30
+ end
31
+
32
+ def query(query_hash = {})
33
+ key = query_hash.sort.map { |k,v| v }.join '.'
34
+ cache[key]
35
+ end
36
+
37
+ def clear_cache
38
+ @cache = nil
39
+ end
40
+
41
+ private
42
+ def key_for(item)
43
+ @item_methods.map { |method| item.send method }.join '.'
44
+ end
45
+
46
+ def cache
47
+ @cache ||= {}
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ module Pinky
2
+ module HasCaches
3
+ def cachable_by(*methods)
4
+ create_cache_for methods
5
+ end
6
+
7
+ def update_caches_with(item_hash, action = :create)
8
+ item = new item_hash
9
+ pinky_caches.each { |cache| cache.update_with item, action }
10
+ item
11
+ end
12
+
13
+ def clear_caches
14
+ pinky_caches.each(&:clear_cache)
15
+ end
16
+
17
+ private
18
+ def create_cache_for(method_names)
19
+ cache = Cache.new method_names
20
+ pinky_caches_by_name[cache.name] = cache
21
+ end
22
+
23
+ def pinky_caches_by_name
24
+ @pinky_caches_by_name ||= {}
25
+ end
26
+
27
+ def pinky_caches
28
+ pinky_caches_by_name.values
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -1,7 +1,8 @@
1
1
  module Pinky
2
2
  module Model
3
3
  def self.included base
4
- base.extend ModelFetchMethods unless base.is_a? ModelFetchMethods
4
+ base.extend HasCaches unless base.is_a?(HasCaches)
5
+ base.extend ModelFetchMethods unless base.is_a?(ModelFetchMethods)
5
6
  base.extend ClassMethods
6
7
  end
7
8
 
@@ -1,36 +1,57 @@
1
1
  module Pinky
2
2
  module ModelFetchMethods
3
- def self.extended base
4
- base.send :include, ModelNaturalKeyMethods unless base.include? ModelNaturalKeyMethods
5
- base.extend CachableModel unless base.is_a? CachableModel
3
+ def self.extended(base)
4
+ base.extend HasCaches unless base.is_a? HasCaches
6
5
  end
7
6
 
8
- def find natural_key
9
- cache[natural_key.to_s]
7
+ def find(query_hash = {})
8
+ query_hash = { :id => query_hash } unless query_hash.is_a?(Hash)
9
+ cache_name = Cache.name_for(*query_hash.keys)
10
+ raise NotFoundException.new unless cache = pinky_caches_by_name[cache_name]
11
+ found_object = cache.query(query_hash)
12
+ return found_object unless found_object.nil?
13
+ item_hash = lookup_item_hash query_hash
14
+ update_caches_with item_hash
10
15
  end
11
16
 
12
- def fetch_url url, fetch_opts = {}
13
- @fetch_url = url
14
- @fetch_opts = fetch_opts
15
- @response_key = @fetch_opts.delete :response_key
17
+ private
18
+ def lookup_item_hash(query)
19
+ response = HTTParty.get pinky_request_url(query),
20
+ :query => pinky_request_query_params(query),
21
+ :headers => pinky_request_headers(query)
22
+ raise Exception.new 'Error fetching from results' unless response.success?
23
+ response_hash = hash_from_pinky_response(response) rescue nil
24
+ if response_hash.is_a?(Array)
25
+ raise TooManyFoundException.new "More than one model was returned" if response_hash.size > 1
26
+ response_hash = response_hash.first
27
+ end
28
+ raise NotFoundException.new "No model found for query #{query.inspect}" if response_hash.nil?
29
+ response_hash
16
30
  end
17
31
 
18
- private
19
- def from_wire natural_key
20
- url = _fetch_url_for natural_key
21
- response = HTTParty.get url, @fetch_opts
22
- response = JSON.parse(response.body)
23
- raise Exception.new "Error fetching from #{url}#{$/}#{response['errors'].join ','}" unless response['success']
24
- response = response['response']
25
- response = response.fetch @response_key if @response_key
26
- raise NotFoundException.new "No model found for natural_key #{natural_key}" if response.empty?
27
- raise TooManyFoundException.new "More than one model was returned" if response.size > 1
28
- response.first
29
- end
30
-
31
- def _fetch_url_for natural_key
32
- raise Exception.new "You must specify a fetch_url for #{self.name}" unless @fetch_url
33
- @fetch_url.dup.sub /:natural_key/, natural_key.to_s
32
+ def hash_from_pinky_response(response)
33
+ response
34
+ end
35
+
36
+ def pinky_request_hostname(query)
37
+ raise Exception.new 'Please provide a hostname via: pinky_request_hostname'
38
+ end
39
+
40
+ def pinky_request_path(query)
41
+ "/#{name.to_s.downcase}"
42
+ end
43
+
44
+ def pinky_request_headers
45
+ {}
46
+ end
47
+
48
+ def pinky_request_query_params(query)
49
+ #query.map { |k,v| "#{k}=#{v}" }.join('&')
50
+ query
51
+ end
52
+
53
+ def pinky_request_url(query)
54
+ "#{pinky_request_hostname(query)}#{pinky_request_path(query)}"
34
55
  end
35
56
  end
36
57
  end
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "pinky"
8
- s.version = "0.1.4"
8
+ s.version = "0.2.5"
9
9
  s.platform = "java"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.authors = ["Joel Friedman"]
13
- s.date = "2012-12-10"
13
+ s.date = "2012-12-12"
14
14
  s.description = "Cache your api objects in member easier, with associations."
15
15
  s.email = "asher.friedman@gmail.com"
16
16
  s.extra_rdoc_files = [
@@ -28,18 +28,18 @@ Gem::Specification.new do |s|
28
28
  "VERSION",
29
29
  "lib/pinky.rb",
30
30
  "lib/pinky/associations.rb",
31
- "lib/pinky/cachable_model.rb",
31
+ "lib/pinky/cache.rb",
32
32
  "lib/pinky/energizer_bunny/connection.rb",
33
33
  "lib/pinky/energizer_bunny/subscription.rb",
34
34
  "lib/pinky/exceptions.rb",
35
+ "lib/pinky/has_caches.rb",
35
36
  "lib/pinky/model.rb",
36
37
  "lib/pinky/model_fetch_methods.rb",
37
- "lib/pinky/model_natural_key_methods.rb",
38
38
  "pinky.gemspec",
39
39
  "spec/pinky/associations_spec.rb",
40
- "spec/pinky/cachable_model_spec.rb",
40
+ "spec/pinky/cache_spec.rb",
41
+ "spec/pinky/has_caches_spec.rb",
41
42
  "spec/pinky/model_fetch_methods_spec.rb",
42
- "spec/pinky/model_natural_key_methods_spec.rb",
43
43
  "spec/pinky/model_spec.rb",
44
44
  "spec/spec_helper.rb"
45
45
  ]
@@ -21,7 +21,7 @@ module Pinky
21
21
 
22
22
  it 'looks up employee by employee_id' do
23
23
  member = member_klass.new 123
24
- Employee.should_receive(:find).with(123)
24
+ Employee.should_receive(:find).with(:id => 123)
25
25
  FooBar.should_not_receive(:find)
26
26
 
27
27
  member.employee
@@ -29,7 +29,7 @@ module Pinky
29
29
 
30
30
  it 'looks up FooBar by employee_id' do
31
31
  member = member_klass.new 999
32
- FooBar.should_receive(:find).with(999)
32
+ FooBar.should_receive(:find).with(:id => 999)
33
33
  Employee.should_not_receive(:find)
34
34
 
35
35
  member.foobar
@@ -0,0 +1,54 @@
1
+ require File.expand_path '../../spec_helper', __FILE__
2
+ require 'date'
3
+
4
+ module Pinky
5
+ describe Cache do
6
+ let(:cache) { Cache.new :id, :name }
7
+
8
+ context 'Cache#name_for' do
9
+ it { Cache.name_for(:id).should == 'cache_by_id' }
10
+ it { Cache.name_for(:id, :name).should == 'cache_by_id_and_name' }
11
+ it { Cache.name_for(:name, :id).should == 'cache_by_id_and_name' }
12
+ it { cache.name.should == 'cache_by_id_and_name' }
13
+ end
14
+
15
+ context 'as a cache' do
16
+ it 'should be able to add and read an item' do
17
+ item = mock(:id => 123, :name => 'joel')
18
+ cache.empty?.should be_true
19
+ cache.update_with(item, :create)
20
+ cache.empty?.should be_false
21
+ end
22
+
23
+ it 'should update cache' do
24
+ item = mock(:id => 123, :name => 'joel', :foo => 'first')
25
+ cache.update_with(item, :create)
26
+ item = mock(:id => 123, :name => 'joel', :foo => 'second')
27
+ cache.update_with(item, :update)
28
+
29
+ cache.size.should == 1
30
+ cache.query(:id => 123, :name => 'joel').foo.should == 'second'
31
+ end
32
+
33
+ it 'should remove from cache' do
34
+ item = mock(:id => 123, :name => 'joel', :foo => 'first')
35
+ cache.update_with(item, :create)
36
+ cache.update_with(item, :destroy)
37
+ cache.empty?.should be_true
38
+ end
39
+ end
40
+
41
+ context 'queryable' do
42
+ let (:cache_hit_query) { { :name => 'guy', :id => 123 } }
43
+ let (:cache_miss_query) { { :id => 123 } }
44
+
45
+ it 'should respond as being queryable' do
46
+ cache.queryable_for?(cache_hit_query).should be_true
47
+ end
48
+
49
+ it 'should respond as not being queryable' do
50
+ cache.queryable_for?(cache_miss_query).should be_false
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path '../../spec_helper', __FILE__
2
+
3
+ module Pinky
4
+ describe HasCaches do
5
+ context 'with cachable_by declared' do
6
+ klass = Class.new do
7
+ extend Pinky::HasCaches
8
+ cachable_by :token
9
+ cachable_by :id, :foo
10
+
11
+ attr_reader :id, :foo, :bar, :token
12
+ def initialize id, foo, bar, token
13
+ @id, @foo, @bar, @token = id, foo, bar, token
14
+ end
15
+ end
16
+
17
+ it 'will create a Cache for each cachable_by' do
18
+ caches = klass.send(:pinky_caches)
19
+ caches.size.should == 2
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,51 +1,55 @@
1
1
  require File.expand_path '../../spec_helper', __FILE__
2
- require 'date'
3
2
 
4
3
  module Pinky
5
4
  describe ModelFetchMethods do
6
- member_klass = Class.new do
7
- extend Pinky::ModelFetchMethods
8
- natural_key :id
9
- fetch_url 'http://fake.com/member?id=:natural_key', :response_key => 'members',
10
- :headers => { 'Accept' => 'version=1' },
11
- :query => { 'token' => '123' }
12
-
13
- def initialize hash; @hash = hash end
14
- def id; @hash['id'] end
15
- end
16
-
17
- before do
18
- @response = {
19
- :success => true,
20
- :response => {
21
- :members => [
22
- {:id => 4255, :token => 'fakeToken123', :first_name => 'Joel', :last_name => 'Friedman', :email => 'joel@example' }
23
- ]
24
- },
25
- :errors => nil
26
- }.to_json
27
- end
28
-
29
- context '#find' do
30
- before do
31
- url = 'http://fake.com/member?id=4255'
32
- HTTParty.should_receive(:get).with(url,
33
- :headers => { 'Accept' => 'version=1' },
34
- :query => { 'token' => '123' }
35
- ).once.and_return(stub(:body => @response))
5
+ context 'caches' do
6
+ member_klass = Class.new do
7
+ extend Pinky::ModelFetchMethods
8
+ cachable_by :id
9
+
10
+ def initialize hash; @hash = hash end
11
+ def id; @hash[:id] end
12
+
13
+ class << self
14
+ def pinky_request_hostname(query)
15
+ 'http://fake.com'
16
+ end
17
+
18
+ def pinky_request_path(query)
19
+ '/member'
20
+ end
21
+
22
+ def pinky_request_headers(query)
23
+ { 'Accept' => 'version=1' }
24
+ end
25
+ end
36
26
  end
37
27
 
38
- after { member_klass.clear_cache }
39
-
40
- it 'not raise an exception' do
41
- expect { member_klass.find 4255 }.to_not raise_error
28
+ let(:response) {
29
+ {:id => 4255, :token => 'fakeToken123', :first_name => 'Joel', :last_name => 'Friedman', :email => 'joel@example' }.tap { |r| r.stub(:success? => true) }
30
+ }
31
+
32
+ context '#find' do
33
+ before do
34
+ url = 'http://fake.com/member'
35
+ HTTParty.should_receive(:get).with(url,
36
+ :headers => { 'Accept' => 'version=1' },
37
+ :query => { :id => 4255 }
38
+ ).once.and_return(response)
39
+ end
40
+
41
+ after { member_klass.clear_caches }
42
+
43
+ it 'not raise an exception' do
44
+ expect { member_klass.find :id => 4255 }.to_not raise_error
45
+ end
46
+
47
+ it 'caches the object' do
48
+ member_klass.find :id => 4255
49
+ member_klass.find :id => 4255
50
+ end
42
51
  end
43
52
 
44
- it 'caches the object' do
45
- member_klass.find 4255
46
- member_klass.find 4255
47
- end
48
53
  end
49
-
50
54
  end
51
55
  end
@@ -4,8 +4,6 @@ module Pinky
4
4
  describe Model do
5
5
  member_klass = Class.new do
6
6
  include Pinky::Model
7
- natural_key :id
8
-
9
7
  def full_name
10
8
  "#{first_name} #{last_name}"
11
9
  end
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: pinky
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.4
5
+ version: 0.2.5
6
6
  platform: java
7
7
  authors:
8
8
  - Joel Friedman
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-10 00:00:00.000000000 Z
12
+ date: 2012-12-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httparty
@@ -167,18 +167,18 @@ files:
167
167
  - VERSION
168
168
  - lib/pinky.rb
169
169
  - lib/pinky/associations.rb
170
- - lib/pinky/cachable_model.rb
170
+ - lib/pinky/cache.rb
171
171
  - lib/pinky/energizer_bunny/connection.rb
172
172
  - lib/pinky/energizer_bunny/subscription.rb
173
173
  - lib/pinky/exceptions.rb
174
+ - lib/pinky/has_caches.rb
174
175
  - lib/pinky/model.rb
175
176
  - lib/pinky/model_fetch_methods.rb
176
- - lib/pinky/model_natural_key_methods.rb
177
177
  - pinky.gemspec
178
178
  - spec/pinky/associations_spec.rb
179
- - spec/pinky/cachable_model_spec.rb
179
+ - spec/pinky/cache_spec.rb
180
+ - spec/pinky/has_caches_spec.rb
180
181
  - spec/pinky/model_fetch_methods_spec.rb
181
- - spec/pinky/model_natural_key_methods_spec.rb
182
182
  - spec/pinky/model_spec.rb
183
183
  - spec/spec_helper.rb
184
184
  homepage: http://github.com/trunkclub/pinky
@@ -1,23 +0,0 @@
1
- module Pinky
2
- module CachableModel
3
- def self.extended base
4
- base.send :include, ModelNaturalKeyMethods unless base.include? ModelNaturalKeyMethods
5
- end
6
-
7
- def update_cache_with item_hash, action
8
- item = new item_hash
9
- cache.delete item.natural_key
10
- cache[item.natural_key] = item unless action.to_sym == :destroy
11
- item
12
- end
13
-
14
- def clear_cache
15
- @cache = nil
16
- end
17
-
18
- private
19
- def cache
20
- @cache ||= Hash.new { |cache_hash, nat_key| update_cache_with from_wire(nat_key), :create }
21
- end
22
- end
23
- end
@@ -1,34 +0,0 @@
1
- module Pinky
2
- module ModelNaturalKeyMethods
3
- def self.included base
4
- base.extend ClassMethods
5
- end
6
-
7
- def natural_key
8
- natural_key_methods.map { |method_name| send method_name }.join natural_key_separator
9
- end
10
-
11
- private
12
- def natural_key_methods
13
- self.class.natural_key or raise Exception.new('You must specify a natural_key, ex: natural_key :first_name, :last_name')
14
- end
15
-
16
- def natural_key_separator
17
- self.class.natural_key_separator
18
- end
19
-
20
- module ClassMethods
21
- def natural_key *methods
22
- return @natural_key if methods.nil? || methods.empty?
23
- @natural_key = methods
24
- end
25
-
26
- def natural_key_separator sep = nil
27
- return @natural_key_separator || '.' if sep.nil?
28
- @natural_key_separator = sep
29
- end
30
-
31
- end
32
-
33
- end
34
- end
@@ -1,52 +0,0 @@
1
- require File.expand_path '../../spec_helper', __FILE__
2
- require 'date'
3
-
4
- module Pinky
5
- describe CachableModel do
6
- member_klass = Class.new do
7
- extend Pinky::CachableModel
8
- natural_key :id
9
-
10
- def self.cache_hash; cache end
11
-
12
- def initialize hash; @hash = hash end
13
- def id; @hash[:id] end
14
- def last_name; @hash[:last_name] end
15
- end
16
-
17
- before do
18
- @hash = {
19
- :id => 4255,
20
- :token => 'fakeToken123',
21
- :first_name => 'Joel',
22
- :last_name => 'Friedman',
23
- :email => 'joel@example'
24
- }
25
- end
26
-
27
- after { member_klass.clear_cache }
28
-
29
- context 'udpate_cache' do
30
- it 'adds a new item to cache if action is :create' do
31
- member_klass.update_cache_with @hash, :create
32
- member_klass.cache_hash.key? 4255
33
- end
34
-
35
- it 'updates the entry if action is :update' do
36
- member_klass.cache_hash['4255'] = member_klass.new(@hash)
37
- member_klass.update_cache_with @hash.merge(:last_name => 'Foster'), :update
38
-
39
- member_klass.cache_hash.keys.should == ['4255']
40
- member_klass.cache_hash['4255'].last_name.should == 'Foster'
41
- end
42
-
43
- it 'removes the entry if action is :destroy' do
44
- member_klass.cache_hash['4255'] = member_klass.new(@hash)
45
- member_klass.update_cache_with @hash, :destroy
46
-
47
- member_klass.cache_hash.empty?.should be_true
48
- end
49
- end
50
-
51
- end
52
- end
@@ -1,46 +0,0 @@
1
- require File.expand_path '../../spec_helper', __FILE__
2
-
3
- module Pinky
4
- describe ModelNaturalKeyMethods do
5
-
6
- context 'with natural_key declared' do
7
- klass = Class.new do
8
- include Pinky::ModelNaturalKeyMethods
9
- natural_key :id, :foo
10
-
11
- attr_reader :id, :foo, :bar
12
- def initialize id, foo, bar
13
- @id, @foo, @bar = id, foo, bar
14
- end
15
- end
16
-
17
-
18
- it 'should create a natural key' do
19
- instance = klass.new(123, 'hey', 'world')
20
- instance.natural_key.should == '123.hey'
21
- end
22
-
23
- it 'can change the key separator' do
24
- klass.natural_key_separator '---'
25
- instance = klass.new(123, 'hey', 'world')
26
- instance.natural_key.should == '123---hey'
27
- end
28
- end
29
-
30
- context 'without natural_key declared' do
31
- klass = Class.new do
32
- include Pinky::ModelNaturalKeyMethods
33
-
34
- attr_reader :id
35
- def initialize
36
- @id = 123
37
- end
38
- end
39
-
40
- it 'should raise an error' do
41
- instance = klass.new
42
- expect { instance.natural_key }.to raise_error(Exception)
43
- end
44
- end
45
- end
46
- end