pinky 0.1.4-java → 0.2.5-java

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