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.
- 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
|