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
@@ -0,0 +1,39 @@
|
|
1
|
+
module Smooth
|
2
|
+
module Presentable
|
3
|
+
module Controller
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
respond_to :json
|
8
|
+
class_attribute :resource
|
9
|
+
end
|
10
|
+
|
11
|
+
def index
|
12
|
+
records = base_scope.present(params)
|
13
|
+
.as(presenter_format)
|
14
|
+
.to(current_user_role)
|
15
|
+
|
16
|
+
render :json => records.results, :root=> false
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def current_user_role
|
22
|
+
(current_user && current_user.try(:role)) || :default
|
23
|
+
end
|
24
|
+
|
25
|
+
def presenter_format
|
26
|
+
params[:presenter] || params[:presenter_format] || :default
|
27
|
+
end
|
28
|
+
|
29
|
+
def base_scope
|
30
|
+
resource_model
|
31
|
+
end
|
32
|
+
|
33
|
+
def resource_model
|
34
|
+
resource = self.class.resource || params[:resource]
|
35
|
+
"#{ resource }".singularize.camelize.constantize
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "smooth/queryable/converter"
|
2
|
+
require "smooth/queryable/settings"
|
3
|
+
|
4
|
+
module Smooth
|
5
|
+
module Queryable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :smooth_queryable_settings
|
10
|
+
self.smooth_queryable_settings ||= Settings.new()
|
11
|
+
|
12
|
+
Smooth::MetaData.register_resource(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def can_be_queried_by parameter, *args
|
17
|
+
options = args.extract_options!
|
18
|
+
settings = self.smooth_queryable_settings ||= Settings.new()
|
19
|
+
|
20
|
+
settings.apply_query_options(parameter, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# It is expected that each model class will implement
|
24
|
+
# its own query method, which handles all of the various
|
25
|
+
# parameters that make sense for that resource
|
26
|
+
def query(params={})
|
27
|
+
scoped if respond_to?(:scoped)
|
28
|
+
end
|
29
|
+
|
30
|
+
def queryable_keys
|
31
|
+
smooth_queryable_settings.available_query_parameters
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Smooth
|
2
|
+
module Queryable
|
3
|
+
class Settings
|
4
|
+
attr_accessor :query_parameters
|
5
|
+
|
6
|
+
def initialize(options={})
|
7
|
+
@query_parameters = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply_query_options parameter, options={}
|
11
|
+
self.query_parameters[parameter] ||= options
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
self.query_parameters
|
16
|
+
end
|
17
|
+
|
18
|
+
def available_query_parameters
|
19
|
+
self.query_parameters.keys
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/smooth-io.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'smooth/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "smooth-io"
|
6
|
+
s.version = Smooth::Version
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["Jonathan Soeder"]
|
9
|
+
s.email = ["jonathan.soeder@gmail.com"]
|
10
|
+
s.homepage = "http://smooth.io"
|
11
|
+
s.summary = "Smooth persistence"
|
12
|
+
s.description = "Cross platform, syncable persistence"
|
13
|
+
|
14
|
+
s.add_dependency 'activerecord', '>= 3.2.12'
|
15
|
+
s.add_dependency 'activesupport', '>= 3.2.12'
|
16
|
+
s.add_dependency 'redis'
|
17
|
+
s.add_dependency 'redis-namespace'
|
18
|
+
s.add_dependency 'typhoeus'
|
19
|
+
s.add_dependency 'virtus', '0.5.5'
|
20
|
+
s.add_dependency 'faye'
|
21
|
+
s.add_dependency 'sinatra'
|
22
|
+
s.add_dependency 'squeel'
|
23
|
+
|
24
|
+
s.add_development_dependency 'rspec', '~> 2.6.0'
|
25
|
+
s.add_development_dependency 'machinist', '~> 1.0.6'
|
26
|
+
s.add_development_dependency 'faker', '~> 0.9.5'
|
27
|
+
s.add_development_dependency 'sqlite3', '~> 1.3.3'
|
28
|
+
s.add_development_dependency 'rack-test'
|
29
|
+
|
30
|
+
s.files = `git ls-files`.split("\n")
|
31
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
32
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
33
|
+
s.require_paths = ["lib"]
|
34
|
+
|
35
|
+
end
|
data/spec/environment.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Object
|
2
|
+
def reload!
|
3
|
+
require 'machinist/active_record'
|
4
|
+
require 'sham'
|
5
|
+
require 'faker'
|
6
|
+
|
7
|
+
require File.expand_path('../support/schema.rb', __FILE__)
|
8
|
+
require File.expand_path('../support/models.rb', __FILE__)
|
9
|
+
|
10
|
+
require 'smooth'
|
11
|
+
|
12
|
+
true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
reload!
|
17
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Smooth::Backends::ActiveRecord do
|
4
|
+
let(:backend) do
|
5
|
+
Smooth::Backends::ActiveRecord.new(model:Person)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should decorate the model with the smooth query method" do
|
9
|
+
backend.model.should respond_to(:smooth_query)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should perform a query against the model" do
|
13
|
+
backend.query.count.should == 10
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Smooth::Backends::Base do
|
4
|
+
|
5
|
+
let(:backend) { Smooth::Backends::Base.new(namespace:"test") }
|
6
|
+
|
7
|
+
it "should be queryable" do
|
8
|
+
backend.query(attribute:"x-ray").should be_empty
|
9
|
+
backend.create(attribute:"yankee")
|
10
|
+
backend.query(attribute:"yankee").length.should == 1
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return an empty set for a query" do
|
14
|
+
backend.create(attribute:"yankee")
|
15
|
+
backend.query(smooth:"yankee").length.should == 0
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return an empty set for a query" do
|
19
|
+
backend.create(attribute:"smooth")
|
20
|
+
backend.query(attribute:"hotel").length.should == 0
|
21
|
+
backend.query(smooth:"attribute").length.should == 0
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should track the maximum updated at" do
|
25
|
+
backend.maximum_updated_at.should >= Time.now.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should update the maximum updated at timestamp" do
|
29
|
+
current = backend.maximum_updated_at
|
30
|
+
backend.create(attribute:"indigo")
|
31
|
+
backend.maximum_updated_at.should >= current
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should allow me to create records" do
|
35
|
+
result = backend.create(attribute: "alpha")
|
36
|
+
fetched = backend.show(result[:id])
|
37
|
+
fetched.should be_present
|
38
|
+
fetched[:attribute].should == "alpha"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should timestamp creates" do
|
42
|
+
current = Time.now.to_i
|
43
|
+
backend.create(attribute:"lima")[:created_at].should >= current
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should timestamp updates" do
|
47
|
+
result = backend.create(attribute:"x-ray")
|
48
|
+
backend.update(result.merge(:name=>"whiskey"))
|
49
|
+
backend.show(result[:id])[:updated_at].should >= result[:created_at]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should assign an id to records" do
|
53
|
+
result = backend.create(attribute:"hotel")
|
54
|
+
result[:id].should_not be_nil
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should allow me to destroy records" do
|
58
|
+
result = backend.create(attribute:"yankee")
|
59
|
+
backend.destroy(result[:id])
|
60
|
+
backend.show(result[:id]).should be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should show me a record" do
|
64
|
+
result = backend.create(attribute:"walt whitman")
|
65
|
+
result[:id].should be_present
|
66
|
+
backend.show(result[:id]).should be_a(Hash)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should allow me to update records" do
|
70
|
+
result = backend.create(attribute:"foxtrot",name:"wilco")
|
71
|
+
backend.update result.merge(name:"mermaid")
|
72
|
+
fetched = backend.show(result[:id])
|
73
|
+
fetched[:name].should == "mermaid"
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
describe Smooth::Backends::File do
|
5
|
+
before(:all) do
|
6
|
+
@tmpdir = File.join(Dir::tmpdir, "#{ Time.now.to_i }")
|
7
|
+
FileUtils.mkdir_p(@tmpdir)
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:all) do
|
11
|
+
FileUtils.rm_rf(@tmpdir)
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:backend) do
|
15
|
+
Smooth::Backends::File.new(namespace:"test",data_directory: @tmpdir)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should not be throttled by default" do
|
19
|
+
backend.should_not be_throttled
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be throttled after a flush" do
|
23
|
+
backend.send(:flush)
|
24
|
+
backend.should be_throttled
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have a storage path" do
|
28
|
+
backend.storage_path.should include("test.json")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not persist its data on disk after every write" do
|
32
|
+
backend.create(:attribute=>"delta")
|
33
|
+
IO.read(backend.storage_path).should_not include("delta")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should persist its data on disk after being flushed" do
|
37
|
+
backend.create(attribute:"charlie") && backend.send(:flush, true)
|
38
|
+
IO.read(backend.storage_path).should include("charlie")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should flush periodically" do
|
42
|
+
backend.setup_periodic_flushing
|
43
|
+
backend.instance_variable_get("@periodic_flusher").should be_a(Thread)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Swappable Backends" do
|
4
|
+
class ModelHelper < Smooth::Model
|
5
|
+
attribute :id
|
6
|
+
attribute :name
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:records) do
|
10
|
+
3.times.collect do |n|
|
11
|
+
{
|
12
|
+
id: n,
|
13
|
+
name: "person #{ n }"
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:redis) { Smooth::Collection.new(namespace:"test",backend:"redis",model_class: ModelHelper) }
|
19
|
+
let(:redis_namespace) { Smooth::Collection.new(namespace:"test",backend:"redis_namespace", model_class: ModelHelper) }
|
20
|
+
let(:file) { Smooth::Collection.new(namespace:"test",backend:"file",model_class:ModelHelper) }
|
21
|
+
|
22
|
+
before(:each) { Redis.new.flushdb }
|
23
|
+
|
24
|
+
it "should have the correct backend" do
|
25
|
+
redis.backend.should be_a(Smooth::Backends::Redis)
|
26
|
+
redis_namespace.backend.should be_a(Smooth::Backends::RedisNamespace)
|
27
|
+
file.backend.should be_a(Smooth::Backends::File)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should encode everything the same" do
|
31
|
+
records.each do |hash|
|
32
|
+
redis.backend.create(hash)
|
33
|
+
file.backend.create(hash)
|
34
|
+
redis_namespace.backend.create(hash)
|
35
|
+
end
|
36
|
+
|
37
|
+
expected = [
|
38
|
+
"person 0",
|
39
|
+
"person 1",
|
40
|
+
"person 2"
|
41
|
+
].to_set
|
42
|
+
|
43
|
+
redis.models.map(&:name).to_set.should == expected
|
44
|
+
redis_namespace.models.map(&:name).to_set.should == expected
|
45
|
+
file.models.map(&:name).to_set.should == expected
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "fakeredis/rspec"
|
3
|
+
|
4
|
+
describe Smooth::Backends::RedisNamespace do
|
5
|
+
let(:backend) do
|
6
|
+
Smooth::Backends::RedisNamespace.new(namespace:"test", redis: ::Redis.new)
|
7
|
+
end
|
8
|
+
|
9
|
+
before(:each) { backend.connection.flushdb }
|
10
|
+
|
11
|
+
it "should allow me to create records" do
|
12
|
+
result = backend.create(attribute:"alpha")
|
13
|
+
fetched = backend.show(result[:id])
|
14
|
+
fetched.should_not be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should allow me to destroy records" do
|
18
|
+
result = backend.create(attribute:"bravo")
|
19
|
+
fetched = backend.show(result[:id])
|
20
|
+
fetched.should be_present
|
21
|
+
backend.destroy(fetched[:id]).should be_true
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should allow me to update records" do
|
25
|
+
record = backend.create(attribute:"yankee")
|
26
|
+
fetched = backend.show(record[:id])
|
27
|
+
backend.update(fetched.merge(attribute:"hotel"))
|
28
|
+
backend.show(record[:id])[:attribute].should == "hotel"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should allow me to view records" do
|
32
|
+
record = backend.create(attribute:"updawg")
|
33
|
+
backend.show(record[:id])[:attribute].should == "updawg"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should store an id" do
|
37
|
+
record = backend.create(attribute:"supbaby")
|
38
|
+
record[:id].should be_present
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "fakeredis/rspec"
|
3
|
+
|
4
|
+
describe Smooth::Backends::Redis do
|
5
|
+
let(:backend) do
|
6
|
+
Smooth::Backends::Redis.new(namespace:"test")
|
7
|
+
end
|
8
|
+
|
9
|
+
before(:each) { backend.connection.flushdb }
|
10
|
+
|
11
|
+
it "should allow me to create records" do
|
12
|
+
result = backend.create(attribute:"alpha")
|
13
|
+
fetched = backend.show(result[:id])
|
14
|
+
fetched.should_not be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should allow me to destroy records" do
|
18
|
+
result = backend.create(attribute:"bravo")
|
19
|
+
fetched = backend.show(result[:id])
|
20
|
+
fetched.should be_present
|
21
|
+
backend.destroy(fetched[:id]).should be_true
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should allow me to update records" do
|
25
|
+
record = backend.create(attribute:"yankee")
|
26
|
+
fetched = backend.show(record[:id])
|
27
|
+
backend.update(fetched.merge(attribute:"hotel"))
|
28
|
+
backend.show(record[:id])[:attribute].should == "hotel"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should allow me to view records" do
|
32
|
+
record = backend.create(attribute:"updawg")
|
33
|
+
backend.show(record[:id])[:attribute].should == "updawg"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should store an id" do
|
37
|
+
record = backend.create(attribute:"supbaby")
|
38
|
+
record[:id].should be_present
|
39
|
+
end
|
40
|
+
end
|