smooth-io 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rvmrc +1 -0
- data/.travis +6 -0
- data/CNAME +1 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +147 -0
- data/Guardfile +5 -0
- data/LICENSE.md +22 -0
- data/README.md +33 -0
- data/Rakefile +10 -0
- data/bin/smooth +8 -0
- data/doc/presenters.md +47 -0
- data/lib/smooth.rb +56 -0
- data/lib/smooth/adapters/rails_cache.rb +19 -0
- data/lib/smooth/adapters/redis_cache.rb +36 -0
- data/lib/smooth/backends/active_record.rb +56 -0
- data/lib/smooth/backends/base.rb +84 -0
- data/lib/smooth/backends/file.rb +80 -0
- data/lib/smooth/backends/redis.rb +71 -0
- data/lib/smooth/backends/redis_namespace.rb +10 -0
- data/lib/smooth/backends/rest_client.rb +29 -0
- data/lib/smooth/collection.rb +236 -0
- data/lib/smooth/collection/cacheable.rb +12 -0
- data/lib/smooth/collection/query.rb +14 -0
- data/lib/smooth/endpoint.rb +23 -0
- data/lib/smooth/meta_data.rb +33 -0
- data/lib/smooth/meta_data/application.rb +29 -0
- data/lib/smooth/meta_data/inspector.rb +67 -0
- data/lib/smooth/model.rb +93 -0
- data/lib/smooth/presentable.rb +76 -0
- data/lib/smooth/presentable/api_endpoint.rb +49 -0
- data/lib/smooth/presentable/chain.rb +46 -0
- data/lib/smooth/presentable/controller.rb +39 -0
- data/lib/smooth/queryable.rb +35 -0
- data/lib/smooth/queryable/converter.rb +8 -0
- data/lib/smooth/queryable/settings.rb +23 -0
- data/lib/smooth/resource.rb +10 -0
- data/lib/smooth/version.rb +3 -0
- data/smooth-io.gemspec +35 -0
- data/spec/blueprints/people.rb +4 -0
- data/spec/environment.rb +17 -0
- data/spec/lib/backends/active_record_spec.rb +17 -0
- data/spec/lib/backends/base_spec.rb +77 -0
- data/spec/lib/backends/file_spec.rb +46 -0
- data/spec/lib/backends/multi_spec.rb +47 -0
- data/spec/lib/backends/redis_namespace_spec.rb +41 -0
- data/spec/lib/backends/redis_spec.rb +40 -0
- data/spec/lib/backends/rest_client_spec.rb +0 -0
- data/spec/lib/collection/query_spec.rb +9 -0
- data/spec/lib/collection_spec.rb +87 -0
- data/spec/lib/examples/cacheable_collection_spec.rb +27 -0
- data/spec/lib/examples/smooth_model_spec.rb +13 -0
- data/spec/lib/meta_data/application_spec.rb +43 -0
- data/spec/lib/meta_data_spec.rb +32 -0
- data/spec/lib/model_spec.rb +54 -0
- data/spec/lib/presentable/api_endpoint_spec.rb +54 -0
- data/spec/lib/presentable_spec.rb +80 -0
- data/spec/lib/queryable/converter_spec.rb +104 -0
- data/spec/lib/queryable_spec.rb +27 -0
- data/spec/lib/resource_spec.rb +17 -0
- data/spec/lib/smooth_spec.rb +13 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/models.rb +28 -0
- data/spec/support/schema.rb +16 -0
- metadata +359 -0
File without changes
|
@@ -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
|