smooth-io 0.0.5

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.
Files changed (65) hide show
  1. data/.gitignore +2 -0
  2. data/.rvmrc +1 -0
  3. data/.travis +6 -0
  4. data/CNAME +1 -0
  5. data/Gemfile +13 -0
  6. data/Gemfile.lock +147 -0
  7. data/Guardfile +5 -0
  8. data/LICENSE.md +22 -0
  9. data/README.md +33 -0
  10. data/Rakefile +10 -0
  11. data/bin/smooth +8 -0
  12. data/doc/presenters.md +47 -0
  13. data/lib/smooth.rb +56 -0
  14. data/lib/smooth/adapters/rails_cache.rb +19 -0
  15. data/lib/smooth/adapters/redis_cache.rb +36 -0
  16. data/lib/smooth/backends/active_record.rb +56 -0
  17. data/lib/smooth/backends/base.rb +84 -0
  18. data/lib/smooth/backends/file.rb +80 -0
  19. data/lib/smooth/backends/redis.rb +71 -0
  20. data/lib/smooth/backends/redis_namespace.rb +10 -0
  21. data/lib/smooth/backends/rest_client.rb +29 -0
  22. data/lib/smooth/collection.rb +236 -0
  23. data/lib/smooth/collection/cacheable.rb +12 -0
  24. data/lib/smooth/collection/query.rb +14 -0
  25. data/lib/smooth/endpoint.rb +23 -0
  26. data/lib/smooth/meta_data.rb +33 -0
  27. data/lib/smooth/meta_data/application.rb +29 -0
  28. data/lib/smooth/meta_data/inspector.rb +67 -0
  29. data/lib/smooth/model.rb +93 -0
  30. data/lib/smooth/presentable.rb +76 -0
  31. data/lib/smooth/presentable/api_endpoint.rb +49 -0
  32. data/lib/smooth/presentable/chain.rb +46 -0
  33. data/lib/smooth/presentable/controller.rb +39 -0
  34. data/lib/smooth/queryable.rb +35 -0
  35. data/lib/smooth/queryable/converter.rb +8 -0
  36. data/lib/smooth/queryable/settings.rb +23 -0
  37. data/lib/smooth/resource.rb +10 -0
  38. data/lib/smooth/version.rb +3 -0
  39. data/smooth-io.gemspec +35 -0
  40. data/spec/blueprints/people.rb +4 -0
  41. data/spec/environment.rb +17 -0
  42. data/spec/lib/backends/active_record_spec.rb +17 -0
  43. data/spec/lib/backends/base_spec.rb +77 -0
  44. data/spec/lib/backends/file_spec.rb +46 -0
  45. data/spec/lib/backends/multi_spec.rb +47 -0
  46. data/spec/lib/backends/redis_namespace_spec.rb +41 -0
  47. data/spec/lib/backends/redis_spec.rb +40 -0
  48. data/spec/lib/backends/rest_client_spec.rb +0 -0
  49. data/spec/lib/collection/query_spec.rb +9 -0
  50. data/spec/lib/collection_spec.rb +87 -0
  51. data/spec/lib/examples/cacheable_collection_spec.rb +27 -0
  52. data/spec/lib/examples/smooth_model_spec.rb +13 -0
  53. data/spec/lib/meta_data/application_spec.rb +43 -0
  54. data/spec/lib/meta_data_spec.rb +32 -0
  55. data/spec/lib/model_spec.rb +54 -0
  56. data/spec/lib/presentable/api_endpoint_spec.rb +54 -0
  57. data/spec/lib/presentable_spec.rb +80 -0
  58. data/spec/lib/queryable/converter_spec.rb +104 -0
  59. data/spec/lib/queryable_spec.rb +27 -0
  60. data/spec/lib/resource_spec.rb +17 -0
  61. data/spec/lib/smooth_spec.rb +13 -0
  62. data/spec/spec_helper.rb +23 -0
  63. data/spec/support/models.rb +28 -0
  64. data/spec/support/schema.rb +16 -0
  65. metadata +359 -0
File without changes
@@ -0,0 +1,9 @@
1
+ require "spec_helper"
2
+
3
+ describe Smooth::Collection::Query do
4
+ let(:query) { Smooth::Collection::Query.new(key1:"value1", key2:"value2") }
5
+
6
+ it "should generate a cache key" do
7
+ query.cache_key.should == "key1:value1/key2:value2"
8
+ end
9
+ end
@@ -0,0 +1,87 @@
1
+ require "spec_helper"
2
+
3
+ describe Smooth::Collection do
4
+ class ModelHelper < Smooth::Model
5
+ attribute :id, String
6
+ attribute :name, String
7
+ end
8
+
9
+ let(:collection) { Smooth::Collection.new(namespace: "test", model_class: ModelHelper) }
10
+
11
+ before(:all) do
12
+ 5.times {|n| collection.backend.create(name:"Item #{ n }")}
13
+ end
14
+
15
+ it "should set an id on the model when it is saved" do
16
+ model = collection.add(name:"Item 9")
17
+ $k = true
18
+ model.sync
19
+ $k = false
20
+
21
+ model.id.should_not be_nil
22
+ end
23
+
24
+ it "should add a model to the collection but not save it" do
25
+ model = collection.add(name: "Item 8")
26
+ model.should be_a(Smooth::Model)
27
+ model.name.should == "Item 8"
28
+ model.id.should be_nil
29
+ end
30
+
31
+ it "should have a file backend by default" do
32
+ collection.backend.should be_a(Smooth::Backends::File)
33
+ end
34
+
35
+ it "should have the concept of a url" do
36
+ collection.url.should match(/test/)
37
+ end
38
+
39
+ it "should fetch some models" do
40
+ collection.fetch()
41
+ collection.models.should_not be_empty
42
+ end
43
+
44
+ it "should allow me to query it" do
45
+ collection.query(name: "Item 0").first[:name].should == "Item 0"
46
+ end
47
+
48
+ it "should allow me to reset it" do
49
+ collection.fetch()
50
+ collection.reset([{one:1}])
51
+ collection.length.should == 1
52
+ end
53
+
54
+ it "should support redis backends" do
55
+ backend = Smooth::Collection.new(backend:"redis",namespace:"test").backend
56
+ backend.should respond_to(:index)
57
+ end
58
+
59
+ it "should support active record backends" do
60
+ lambda {
61
+ Smooth::Collection.new(model: Person)
62
+ }.should_not raise_error
63
+ end
64
+
65
+ it "should empty the models when resetting" do
66
+ collection.reset([])
67
+ collection.models.should be_empty
68
+ end
69
+
70
+ describe "The find methods" do
71
+ it "should find by id" do
72
+ collection.find(1).name.should == "Item 0"
73
+ end
74
+
75
+ it "should find by name" do
76
+ collection.find_by(:name,"Item 0").id.to_i.should == 1
77
+ end
78
+ end
79
+
80
+ describe "The Sync Interface" do
81
+ it "should read the models from the collection" do
82
+ collection.reset([])
83
+ collection.sync
84
+ collection.models.length.should >= 5
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Cacheable Collections" do
4
+ let(:collection) do
5
+ Smooth::Collection.new(backend: "active_record", backend_options:{model:Person}, cache: "redis")
6
+ end
7
+
8
+ let(:hit) do
9
+ query = Smooth::Collection::Query.new(smooth:"baby",yeah:"son")
10
+ collection.send(:cache_adapter).write(query.cache_key, "[{id:1}]")
11
+ query
12
+ end
13
+
14
+ let(:miss) do
15
+ Smooth::Collection::Query.new(miss:"this",query:"awww")
16
+ end
17
+
18
+ it "should avoid the backend in a cache hit" do
19
+ collection.backend.should_not_receive(:query)
20
+ collection.query(hit)
21
+ end
22
+
23
+ it "should fallback to the backend if there is a cache miss" do
24
+ collection.backend.should_receive(:query)
25
+ collection.query(miss)
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ require "spec_helper"
2
+
3
+ class SmoothModel < Smooth::Model
4
+ attribute :name, String
5
+ attribute :age, Integer
6
+ end
7
+
8
+ describe "The Smooth Model system" do
9
+ it "should respond to the attribute defined" do
10
+ model = SmoothModel.new(name:"Jonathan Soeder",age: Time.now.year - 1980)
11
+ model.name.should == "Jonathan Soeder"
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ require "spec_helper"
2
+
3
+ describe Smooth::MetaData::Application do
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ Smooth::MetaData::Application.new
8
+ end
9
+
10
+ class MetaHelperOne < ActiveRecord::Base
11
+ include Smooth::Queryable
12
+ include Smooth::Presentable
13
+ self.table_name = :people
14
+ can_be_queried_by :name
15
+ end
16
+
17
+ class MetaHelperTwo < ActiveRecord::Base
18
+ include Smooth::Queryable
19
+ include Smooth::Presentable
20
+
21
+ self.table_name = :people
22
+ can_be_queried_by :name
23
+ end
24
+
25
+ it "should return a list of resources" do
26
+ get "/"
27
+ response = JSON.parse(last_response.body)
28
+ response.should be_a(Hash)
29
+ response.keys.should include("MetaHelperOne")
30
+ end
31
+
32
+ it "should show info for a resource" do
33
+ get "/meta_helper_one"
34
+ response = JSON.parse(last_response.body)
35
+ response.keys.should include("queryable")
36
+ response.keys.should include("presentable")
37
+ end
38
+
39
+ it "should return 404" do
40
+ get "/supboo"
41
+ last_response.status.should == 404
42
+ end
43
+ end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ describe Smooth::MetaData do
4
+
5
+ class MetaDataHelper < ActiveRecord::Base
6
+ self.table_name = "people"
7
+
8
+ include Smooth::Presentable
9
+ include Smooth::Queryable
10
+
11
+ can_be_queried_by :name
12
+ end
13
+
14
+ let(:meta_data) { Smooth::MetaData["MetaDataHelper"] }
15
+
16
+ it "should be able to tell me all of the smooth resources" do
17
+ Smooth::MetaData.available_resources.should include("MetaDataHelper")
18
+ end
19
+
20
+ it "should tell me all of the presenters for a given resource" do
21
+ meta_data.presenters.should include(:default)
22
+ end
23
+
24
+ it "should tell me all of the columns a resource can be queried by" do
25
+ meta_data.queryable_parameters.should include(:name)
26
+ end
27
+
28
+ it "should give me a hash object of all of the query settings" do
29
+ meta_data.queryable_settings.should be_a(Hash)
30
+ end
31
+ end
32
+
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe Smooth::Model do
4
+
5
+ class SmoothModelHelper < Smooth::Model
6
+ attribute :id, String
7
+ attribute :name, String
8
+ end
9
+
10
+ let(:model) { collection.models[0] || collection.add(name:"Item 1",id:1) }
11
+
12
+ let(:collection) do
13
+ collection = Smooth::Collection.new(namespace:"model:spec",backend:"file", model_class: SmoothModelHelper)
14
+
15
+ 5.times do |n|
16
+ id = n + 1
17
+ collection.backend.create(name:"Item #{ n }", id: id)
18
+ end
19
+
20
+ collection
21
+ end
22
+
23
+ it "should have basic attributes as configured" do
24
+ SmoothModelHelper.new(name:"sup baby").name.should == "sup baby"
25
+ end
26
+
27
+ it "should read its attributes from the collections data source given an id" do
28
+ new_model = SmoothModelHelper.new({id:1}, collection: collection)
29
+ new_model.fetch
30
+ new_model.name.should == "Item 0"
31
+ end
32
+
33
+ it "should save and update" do
34
+ model = SmoothModelHelper.new({name:"j-money baby"}, collection: collection)
35
+ model.name = "sup my boo"
36
+ model.save
37
+
38
+ collection.fetch
39
+ collection.models.detect {|m| m.name == "sup my boo" }.should be_present
40
+ end
41
+
42
+ it "should save and create" do
43
+ model = SmoothModelHelper.new({name:"j-money"}, collection: collection)
44
+ model.save
45
+
46
+ collection.models.detect {|m| m.name == "j-money" }.should be_present
47
+ model.id.to_i.should >= 1
48
+ end
49
+
50
+ it "should delegate its sync method to the collection" do
51
+ collection.should_receive(:sync)
52
+ model.sync(:read, {id:1}, {})
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe Smooth::Presentable::ApiEndpoint do
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ Smooth::Presentable::ApiEndpoint
8
+ end
9
+
10
+
11
+ class ApiHelper < ActiveRecord::Base
12
+ self.table_name = :people
13
+ include Smooth::Presentable
14
+
15
+ def sup
16
+ "hi #{ name }"
17
+ end
18
+ end
19
+
20
+ class ApiHelperPresenter
21
+ def self.custom
22
+ [:name,{key:"sup"}]
23
+ end
24
+ end
25
+
26
+
27
+ before(:all) do
28
+ ApiHelper.delete_all
29
+ ApiHelper.create(name:"Tahereh")
30
+ end
31
+
32
+ it "should return 404 for an invalid resource" do
33
+ get "/sup_boo"
34
+ last_response.status.should == 404
35
+ end
36
+
37
+ it "should allow me to query a resource" do
38
+ get "/api_helper"
39
+ last_response.status.should == 200
40
+ response = JSON.parse(last_response.body)
41
+ response.should be_a(Array)
42
+ response.should_not be_empty
43
+ end
44
+
45
+ xit "should allow me to specify a custom presenter" do
46
+ get "/api_helper/custom"
47
+
48
+ last_response.status.should == 200
49
+ response = JSON.parse(last_response.body)
50
+ response.should be_a(Array)
51
+ response.should_not be_empty
52
+ end
53
+ end
54
+
@@ -0,0 +1,80 @@
1
+ require "spec_helper"
2
+
3
+ describe Smooth::Presentable do
4
+ before(:all) do
5
+ Person.send(:include, Smooth::Presentable)
6
+ end
7
+
8
+ it "should register the resource with the smooth metadata registry" do
9
+ Smooth::MetaData.available_resources.should include("Person")
10
+ end
11
+
12
+ it "should allow me to present a resource as a given format" do
13
+ records = Person.present({}).as(:default)
14
+ records.results.should be_a(Array)
15
+ end
16
+
17
+ context "Custom Presenters" do
18
+ class PresentableHelper < ActiveRecord::Base
19
+ self.table_name = "people"
20
+
21
+ include Smooth::Presentable
22
+ include Smooth::Queryable
23
+
24
+ def friend
25
+ Friend.new(name:"Jonathan Soeder")
26
+ end
27
+ end
28
+
29
+ class PresentableHelperPresenter
30
+ def self.default
31
+ [:name]
32
+ end
33
+
34
+ def self.custom
35
+ [
36
+ :name,
37
+ {
38
+ :attribute => :friend,
39
+ :method => :friend,
40
+ :presenter => :custom
41
+ }
42
+ ]
43
+ end
44
+ end
45
+
46
+ class Friend < ActiveRecord::Base
47
+ self.table_name = "people"
48
+ include Smooth::Presentable
49
+ include Smooth::Queryable
50
+ end
51
+
52
+ class FriendPresenter
53
+ def self.custom
54
+ [:name]
55
+ end
56
+ end
57
+
58
+ it "should use a role based presenter format if specified" do
59
+ FriendPresenter.should_receive(:role_custom).at_least(:once)
60
+ Friend.present({}).as(:custom).to(:role).to_a
61
+ end
62
+
63
+ it "should fall back to the non-role based presenter if there isn't a format specifically defined for the role" do
64
+ FriendPresenter.should_receive(:custom).at_least(:once)
65
+ Friend.present({}).as(:custom).to(:default).to_a
66
+ end
67
+
68
+ it "should use the default format if an unknown format is requested" do
69
+ lambda {
70
+ PresentableHelper.new(name:"Sup").present_as(:sheeeeit)
71
+ }.should_not raise_error
72
+ end
73
+
74
+ it "should allow me to nest other presenter calls" do
75
+ record = PresentableHelper.new(name:"Tupac")
76
+ presented = record.present_as(:custom)
77
+ presented[:friend][:name].should == "Jonathan Soeder"
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,104 @@
1
+ require "spec_helper"
2
+
3
+ class MyCollection
4
+ include Smooth::Queryable
5
+ end
6
+
7
+ describe Smooth::Queryable do
8
+
9
+ describe "Equality" do
10
+ it "should handle equality" do
11
+ results = MyCollection.query({title:"Test"});
12
+ end
13
+
14
+ it "should handle equality" do
15
+ results = MyCollection.query({ title: {"$equal"=>"Test"} });
16
+ end
17
+ end
18
+
19
+ # MyCollection.query({ colors: {$contains: "red"} });
20
+ xit "should support $contains" do
21
+ end
22
+
23
+ xit "should support $ne" do
24
+ MyCollection.query({ title: {"$ne"=>"Test"} })
25
+ end
26
+
27
+ xit "should support $lt" do
28
+ MyCollection.query({ likes: {"$lt"=>10} })
29
+ end
30
+
31
+ xit "should support $lte" do
32
+ MyCollection.query({ likes: {"$lte"=>10} })
33
+ end
34
+
35
+ xit "should support $gt" do
36
+ MyCollection.query({ likes: {"$gt"=>10} })
37
+ end
38
+
39
+ xit "should support $gte" do
40
+ MyCollection.query({ likes: {"$gte"=>10} })
41
+ end
42
+
43
+ xit "should support $between" do
44
+ MyCollection.query({ likes: {"$between"=>[5,15] } })
45
+ end
46
+
47
+ xit "should support $in" do
48
+ MyCollection.query({ title: {"$in"=>["About", "Home", "Contact"] } });
49
+ end
50
+
51
+ xit "should support $nin" do
52
+ MyCollection.query({ title: { "$nin"=>["About", "Home", "Contact"] } });
53
+ end
54
+
55
+ xit "should support $all" do
56
+ end
57
+
58
+ xit "should support $any" do
59
+ end
60
+
61
+ xit "should support $size" do
62
+ end
63
+
64
+ xit "should support $exists" do
65
+ end
66
+
67
+ xit "should support $has" do
68
+ end
69
+
70
+ xit "should support $like" do
71
+ MyCollection.query({ title: {"$like"=> "Test"} });
72
+ end
73
+
74
+ xit "should support $likeI" do
75
+ MyCollection.query({ title: {"$likeI"=> "Test" } });
76
+ end
77
+
78
+ xit "should support $regex" do
79
+ MyCollection.query({ content: {"$regex"=> /coffeescript/i } });
80
+ end
81
+
82
+ xit "should support $regex" do
83
+ MyCollection.query({ content: /coffeescript/i });
84
+ end
85
+
86
+ describe "Combined Queries" do
87
+ xit "should support $and" do
88
+ MyCollection.query({ "$and"=> { title: {"$like"=> "News"}, likes: {"$gt"=> 10}}});
89
+ MyCollection.query({ title: {"$like"=> "News"}, likes: {"$gt"=> 10} });
90
+ end
91
+
92
+ xit "should support $or" do
93
+ MyCollection.query({ "$or"=> { title: { "$like"=> "News"}, likes: { "$gt"=> 10}}});
94
+ end
95
+
96
+ xit "should support $nor" do
97
+ MyCollection.query({"$nor"=> { title: {"$like"=> "News"}, likes: {"$gt"=> 10}}});
98
+ end
99
+
100
+ xit "should support $not" do
101
+ MyCollection.query({"$not"=> { title: { "$like"=> "News"}, likes: { "$gt"=> 10}}});
102
+ end
103
+ end
104
+ end