smooth-io 0.0.5

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