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
@@ -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,8 @@
1
+ module Smooth
2
+ module Queryable
3
+ class Converter
4
+ def initialize query={}
5
+ end
6
+ end
7
+ end
8
+ 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
@@ -0,0 +1,10 @@
1
+ module Smooth
2
+ module Resource
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include Smooth::Queryable
7
+ include Smooth::Presentable
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Smooth
2
+ Version = '0.0.5'
3
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ Person.blueprint do
2
+ name
3
+ salary
4
+ end
@@ -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