smooth 2.0.1 → 2.0.2

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -0
  3. data/Gemfile +1 -2
  4. data/README.md +150 -5
  5. data/Rakefile +16 -0
  6. data/app/assets/javascripts/smooth/index.js +5152 -0
  7. data/bin/smooth +9 -0
  8. data/{app/assets/javascripts/smooth → developer-tools}/.keep +0 -0
  9. data/developer-tools/bower.json +8 -0
  10. data/developer-tools/config.ru +3 -0
  11. data/developer-tools/dist/08d606864d3ad3f0b98660d391f5a1c2.gif +0 -0
  12. data/developer-tools/dist/2d66bcdc27cd89f71068e98a7a929712.gif +0 -0
  13. data/developer-tools/dist/3e9816417b11485d454f9b3662b06e7b.eot +0 -0
  14. data/developer-tools/dist/47de617fd1d745ad120ccb9e2924b98c.gif +0 -0
  15. data/developer-tools/dist/5ae23ad29b67289a1375d2043e289c52.eot +0 -0
  16. data/developer-tools/dist/60c2a8500e63bf211b7df9608f7613ea.svg +450 -0
  17. data/developer-tools/dist/645f50ba6c1e56f078fa018855d97eb0.gif +0 -0
  18. data/developer-tools/dist/71ab514d1cedda303417ad7a06472fea.ttf +0 -0
  19. data/developer-tools/dist/8cca2f02b0af2da365ff4d1755f29146.ttf +0 -0
  20. data/developer-tools/dist/939cf252f0eb4efbd2d170c974411c49.gif +0 -0
  21. data/developer-tools/dist/9af25aaeb6ca6d08d213b04841813eb5.gif +0 -0
  22. data/developer-tools/dist/b683029bafe0305ac2234038a03e1541.woff +0 -0
  23. data/developer-tools/dist/c9dec22105ad9330c811599b8b6464f8.woff +0 -0
  24. data/developer-tools/dist/ca279c55a51ab2641c4712a333633581.gif +0 -0
  25. data/developer-tools/dist/client.js +5152 -0
  26. data/developer-tools/dist/f5b27137d3f5e9b1d91b16b37386dd03.gif +0 -0
  27. data/developer-tools/dist/f99a231ed57ee113b50b1c3e9f9fcdc3.svg +399 -0
  28. data/developer-tools/dist/index.html +18 -0
  29. data/developer-tools/dist/inspector.js +38432 -0
  30. data/developer-tools/dist/jquery.min.js +9190 -0
  31. data/developer-tools/package.json +39 -0
  32. data/developer-tools/server.js +14 -0
  33. data/developer-tools/src/client.coffee +21 -0
  34. data/developer-tools/src/client/collection.coffee +14 -0
  35. data/developer-tools/src/client/model.coffee +11 -0
  36. data/developer-tools/src/client/resource.coffee +132 -0
  37. data/{app/controllers/.keep → developer-tools/src/client/runner.coffee} +0 -0
  38. data/developer-tools/src/dependencies.coffee +7 -0
  39. data/developer-tools/src/inspector.cjsx +49 -0
  40. data/developer-tools/src/inspector/models/interface_collection.coffee +31 -0
  41. data/developer-tools/src/inspector/pages/index.cjsx +31 -0
  42. data/developer-tools/src/inspector/pages/resources.cjsx +5 -0
  43. data/developer-tools/src/inspector/views/grid_sort.cjsx +23 -0
  44. data/developer-tools/src/inspector/views/icon_heading.cjsx +15 -0
  45. data/developer-tools/src/inspector/views/resource_card.cjsx +34 -0
  46. data/developer-tools/src/inspector/views/sidebar.cjsx +12 -0
  47. data/developer-tools/src/inspector/views/toolbar.cjsx +17 -0
  48. data/developer-tools/src/styles/index.scss +136 -0
  49. data/developer-tools/src/styles/views.scss +13 -0
  50. data/developer-tools/src/util.coffee +48 -0
  51. data/developer-tools/webpack.config.js +56 -0
  52. data/developer-tools/webpack.hot.config.js +65 -0
  53. data/lib/smooth.rb +209 -28
  54. data/lib/smooth/active_record/adapter.rb +24 -0
  55. data/lib/smooth/api.rb +272 -18
  56. data/lib/smooth/api/policy.rb +2 -2
  57. data/lib/smooth/api/tracking.rb +4 -4
  58. data/lib/smooth/application.rb +66 -0
  59. data/lib/smooth/cache.rb +1 -1
  60. data/lib/smooth/command.rb +267 -18
  61. data/lib/smooth/command/async_worker.rb +27 -0
  62. data/lib/smooth/command/instrumented.rb +6 -4
  63. data/lib/smooth/command/run_proxy.rb +21 -0
  64. data/lib/smooth/configuration.rb +63 -8
  65. data/lib/smooth/documentation.rb +3 -6
  66. data/lib/smooth/dsl.rb +1 -36
  67. data/lib/smooth/dsl_adapter.rb +34 -0
  68. data/lib/smooth/event.rb +8 -4
  69. data/lib/smooth/event/proxy.rb +9 -0
  70. data/lib/smooth/event/relay.rb +38 -0
  71. data/lib/smooth/example.rb +1 -1
  72. data/lib/smooth/ext/core.rb +16 -0
  73. data/lib/smooth/model_adapter.rb +31 -0
  74. data/lib/smooth/query.rb +143 -13
  75. data/lib/smooth/resource.rb +227 -52
  76. data/lib/smooth/resource/router.rb +217 -0
  77. data/lib/smooth/resource/templating.rb +62 -0
  78. data/lib/smooth/resource/tracking.rb +1 -1
  79. data/lib/smooth/response.rb +73 -0
  80. data/lib/smooth/serializer.rb +102 -11
  81. data/lib/smooth/user_adapter.rb +83 -0
  82. data/lib/smooth/util.rb +17 -0
  83. data/lib/smooth/version.rb +1 -1
  84. data/smooth.gemspec +6 -2
  85. data/spec/acceptance/books_routes_spec.rb +50 -0
  86. data/spec/acceptance/embedded_relationships_spec.rb +26 -0
  87. data/spec/dummy/app/apis/application_api.rb +8 -3
  88. data/spec/dummy/app/commands/create_book.rb +5 -0
  89. data/spec/dummy/app/models/book.rb +1 -0
  90. data/spec/dummy/app/models/library.rb +2 -0
  91. data/spec/dummy/app/models/user.rb +2 -0
  92. data/spec/dummy/app/queries/book_query.rb +13 -0
  93. data/spec/dummy/app/resources/{books.rb → books_definition.rb} +37 -12
  94. data/spec/dummy/db/migrate/20140824215902_create_users.rb +10 -0
  95. data/spec/dummy/db/migrate/20140826193259_create_libraries.rb +10 -0
  96. data/spec/dummy/db/schema.rb +8 -1
  97. data/spec/lib/smooth/api/async_spec.rb +21 -0
  98. data/spec/lib/smooth/api_spec.rb +8 -0
  99. data/spec/lib/smooth/command_spec.rb +87 -6
  100. data/spec/lib/smooth/configuration_spec.rb +4 -0
  101. data/spec/lib/smooth/event/relay_spec.rb +33 -0
  102. data/spec/lib/smooth/event_spec.rb +5 -8
  103. data/spec/lib/smooth/query_spec.rb +42 -0
  104. data/spec/lib/smooth/resource/router_spec.rb +14 -0
  105. data/spec/lib/smooth/resource_spec.rb +33 -1
  106. data/spec/lib/smooth/serializer_spec.rb +20 -0
  107. data/spec/lib/smooth/templating_spec.rb +23 -0
  108. data/spec/lib/smooth/util_spec.rb +22 -0
  109. data/spec/spec_helper.rb +1 -1
  110. metadata +151 -17
  111. data/app/helpers/.keep +0 -0
  112. data/app/mailers/.keep +0 -0
  113. data/app/models/.keep +0 -0
  114. data/app/views/.keep +0 -0
  115. data/spec/dummy/db/development.sqlite3 +0 -0
  116. data/spec/dummy/db/test.sqlite3 +0 -0
@@ -0,0 +1,83 @@
1
+ module Smooth
2
+ module UserAdapter
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ base.send(:attr_accessor, :last_request_params, :last_request_headers)
6
+
7
+ base.send(:before_create, -> { generate_token(Smooth.config.auth_token_column) })
8
+ end
9
+
10
+ def generate_token(column)
11
+ if self.class.column_names.include?(column.to_s)
12
+ write_attribute(column, SecureRandom.urlsafe_base64)
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def find_for_smooth_api_request(id, passed_authentication_token)
18
+ where(id: id, authentication_token: passed_authentication_token).first
19
+ end
20
+
21
+ def find_for_token_authentication(passed_authentication_token)
22
+ id, token = passed_authentication_token.split(':')
23
+ find_for_smooth_api_request(id, token)
24
+ end
25
+
26
+ def anonymous(params = nil, headers = nil)
27
+ User.new.tap do |user|
28
+ user.last_request_params = params if params
29
+ user.last_request_headers = headers if headers
30
+ user.making_anonymous_request = true
31
+ end
32
+ end
33
+ end
34
+
35
+ def making_anonymous_request=(setting)
36
+ @making_anonymous_request = !!(setting)
37
+ end
38
+
39
+ def anonymous?
40
+ !!(@making_anonymous_request)
41
+ end
42
+
43
+ def smooth_authentication_token
44
+ read_attribute(:authentication_token)
45
+ "#{ id }:#{ token }"
46
+ end
47
+
48
+ # Allows for using the current_user making an API request
49
+ # as the source of all queries, and commands run against
50
+ # Smooth resources.
51
+ #
52
+ # Example:
53
+ #
54
+ # current_user.smooth.query("books.mine", published_before: 2014)
55
+ #
56
+ # Piping all queries to the Smooth Resources through the same interface
57
+ # makes implementing a declarative, role based access control policy pretty
58
+ # easy.
59
+ #
60
+ # You could even add the following methods to all of your ApplicationController
61
+ #
62
+ # Example:
63
+ #
64
+ # class ApplicationController < ActionController::Base
65
+ # def run_query *args, &block
66
+ # current_user.smooth.send(:query, *args, &block)
67
+ # end
68
+ #
69
+ # def run_command *args, &block
70
+ # current_user.smooth.send(:run_command, *args, &block)
71
+ # end
72
+ # end
73
+ #
74
+ # class BooksController < ApplicationController
75
+ # def index
76
+ # render :json => run_query("books", params)
77
+ # end
78
+ # end
79
+ def smooth(api = :default)
80
+ Smooth.fetch_api(api).as(self)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,17 @@
1
+ module Smooth
2
+ module Util
3
+ extend self
4
+
5
+ def uri_template(url_pattern)
6
+ URITemplate.new(:colon, url_pattern)
7
+ end
8
+
9
+ def expand_url_template(uri_template, vars = {})
10
+ uri_template.expand(vars)
11
+ end
12
+
13
+ def extract_url_vars(uri_template, actual_url)
14
+ uri_template.extract(actual_url).tap(&:symbolize_keys!)
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module Smooth
2
- VERSION = "2.0.1"
2
+ VERSION = '2.0.2'
3
3
  end
@@ -21,9 +21,13 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency 'hashie'
22
22
  spec.add_dependency 'activesupport', '>= 4.0.0'
23
23
  spec.add_dependency 'activerecord', '>= 4.0.0'
24
- spec.add_dependency 'active_model_serializers'
25
- spec.add_dependency 'faker'
24
+ spec.add_dependency 'active_model_serializers', '~> 0.8.0'
25
+ spec.add_dependency 'ffaker'
26
+ spec.add_dependency 'factory_girl'
26
27
  spec.add_dependency 'mutations'
28
+ spec.add_dependency 'sinatra'
29
+ spec.add_dependency 'escape_utils'
30
+ spec.add_dependency 'uri_template'
27
31
 
28
32
  spec.add_development_dependency "bundler", "~> 1.3"
29
33
  spec.add_development_dependency "rake"
@@ -0,0 +1,50 @@
1
+ require "spec_helper"
2
+
3
+ describe "The Books Resource Routes" do
4
+ let(:books) { Smooth("Books") }
5
+
6
+ let(:session) do
7
+ Rack::MockSession.new(books.api.sinatra)
8
+ end
9
+
10
+ let(:client) do
11
+ Rack::Test::Session.new(session)
12
+ end
13
+
14
+ it "should make a request to the show action" do
15
+ book = Book.create(title:"Cristian The LionHeart")
16
+ response = client.get("/books/#{ book.id }")
17
+ json = JSON.parse(response.body) rescue {}
18
+ book = json.fetch("book")
19
+
20
+ expect(response.status).to eq(200)
21
+ expect(book["title"]).to eq("Cristian The LionHeart")
22
+ end
23
+
24
+ it "should make a request to the books query" do
25
+ Book.create(title:"Luca The Coming Champ", year_published: 1895)
26
+ response = client.get("/books", title: "Luca")
27
+ json = JSON.parse(response.body) rescue {}
28
+
29
+ expect(json).not_to be_empty
30
+ expect(response.status).to eq(200)
31
+ end
32
+
33
+ it "should make a request to the create command" do
34
+ response = client.post("/books", title: "The Biography of Jon Soeder")
35
+
36
+ json = JSON.parse(response.body) rescue {}
37
+
38
+ book = json.fetch("book")
39
+
40
+ expect(response.status).to eq(200)
41
+ expect(book).to have_key("author_id")
42
+ expect(book["id"]).not_to be_nil
43
+ expect(book["title"]).to eq("The Biography of Jon Soeder")
44
+ end
45
+
46
+ it "should return errors if i don't include the right params" do
47
+ response = client.post("/books")
48
+ expect(response.status).not_to eq(200)
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ require "spec_helper"
2
+
3
+ describe "The Books Resource Routes" do
4
+ let(:books) { Smooth("Books") }
5
+
6
+ let(:session) do
7
+ Rack::MockSession.new(books.api.sinatra)
8
+ end
9
+
10
+ let(:client) do
11
+ Rack::Test::Session.new(session)
12
+ end
13
+
14
+ it "should fetch objects by ids" do
15
+
16
+ book_ids = 3.times.map do |n|
17
+ Book.create(title:"Book #{ Time.now.to_i }")
18
+ end.slice(0,2).map(&:id).join(',')
19
+
20
+ response =client.get("/books?ids=#{ book_ids }")
21
+ json = JSON.parse(response.body) rescue {}
22
+
23
+ expect(json.length).to eq(2)
24
+ end
25
+ end
26
+
@@ -3,12 +3,17 @@ require 'smooth/dsl'
3
3
  api "My Application" do
4
4
  version :v1
5
5
 
6
+ authentication_strategy :header, "X-AUTH-TOKEN"
7
+
8
+ user_class User do
9
+ include(Smooth::UserAdapter)
10
+ end
11
+
6
12
  desc "Public users include anyone with access to the URL"
7
13
  policy :public_users do
8
-
9
14
  # commands / queries can be set to true or false to allow
10
15
  # all commands and queries defined for the books resource.
11
- allow :books, :commands => false, :queries => true
16
+ #allow :books, :commands => false, :queries => true
12
17
 
13
18
  # we can also pass an array of queries or commands
14
19
  # allow :books, :commands => [:like]
@@ -17,7 +22,7 @@ api "My Application" do
17
22
  desc "Authenticated users register and are given an auth token"
18
23
  policy :logged_in_users do
19
24
  authenticate_with :header => 'X-AUTH-TOKEN', :param => :auth_token
20
- allow :books, :commands => true, :queries => true
25
+ #allow :books, :commands => true, :queries => true
21
26
  end
22
27
 
23
28
  desc "Admin users have the admin flag set to true"
@@ -0,0 +1,5 @@
1
+ class CreateBook < Smooth.config.command_class
2
+ def execute
3
+ model_class.create(title: title)
4
+ end
5
+ end
@@ -1,2 +1,3 @@
1
1
  class Book < ActiveRecord::Base
2
+ belongs_to :author
2
3
  end
@@ -0,0 +1,2 @@
1
+ class Library < ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class User < ActiveRecord::Base
2
+ end
@@ -0,0 +1,13 @@
1
+ class BookQuery < Smooth::Query
2
+ def self.developer_defined_method
3
+ true
4
+ end
5
+ end
6
+
7
+ resource "Books" do
8
+ query do
9
+ def inline_dsl_method
10
+ true
11
+ end
12
+ end
13
+ end
@@ -1,4 +1,5 @@
1
1
  resource "Books" do
2
+ desc "The default serializer for book"
2
3
  serializer do
3
4
  desc "A unique id for the book", :type => :integer
4
5
  attribute :id
@@ -13,11 +14,17 @@ resource "Books" do
13
14
  def computed_property
14
15
  object.created_at
15
16
  end
17
+
18
+ desc "Another way for doing computed properties"
19
+ computed(:another_computed_property) do
20
+ object.created_at.to_i
21
+ end
16
22
  end
17
23
 
18
24
  # This will create a class 'UpdateBook'. The execute method
19
25
  # is open for definition by the developer.
20
- command :update, "Update a book's attributes" do
26
+ desc "Update a book's attributes"
27
+ command :update do
21
28
  # Will ensure the command is run with
22
29
  # Book.accessible_to(current_user).find(id).
23
30
  scope :accessible_to
@@ -29,17 +36,21 @@ resource "Books" do
29
36
  string :title
30
37
  end
31
38
  end
39
+
40
+ execute(:update)
32
41
  end
33
42
 
34
- command :create, "Add a new book to the library" do
43
+ desc "Create a book"
44
+ command :create do
35
45
  scope :accessible_to
36
46
 
37
47
  params do
38
- string :title
48
+ string :title, faker: 'app.author'
39
49
  end
40
50
  end
41
51
 
42
- command :like, 'Toggle liking on/off for a book' do
52
+ desc "Toggle whether you like a book or not"
53
+ command :like do
43
54
  scope :all
44
55
 
45
56
  params do
@@ -52,18 +63,24 @@ resource "Books" do
52
63
  end
53
64
  end
54
65
 
66
+ desc "Here we just define a relationship to a known class"
67
+ command :criticize, (CriticizeBook = Class.new(Smooth::Command))
68
+
55
69
  # This will create a class 'BookQuery'. The build_scope method
56
70
  # is open for definition by the developer.
57
71
  query do
58
- start_from :scope => :accessible_to
72
+ scope :accessible_to
59
73
 
60
74
  params do
61
75
  desc "The year the book was published (example: YYYY)"
62
- integer :year_published
76
+ integer :year_published, operator: :gte
77
+
78
+ desc "A partial string to filter the title by"
79
+ string :title, operator: :like
63
80
  end
64
81
 
65
82
  role :admin do
66
- start_from :scope => :all
83
+ scope :all
67
84
  end
68
85
  end
69
86
 
@@ -72,19 +89,27 @@ resource "Books" do
72
89
  # under /api/v1/books, /api/v1/books/1 etc
73
90
  routes do
74
91
  desc "List all books"
75
- get "/books", :to => :query
92
+ get "/books", :to => :query, :as => :list_books
76
93
 
77
94
  desc "Show an individual book"
78
- show "/books/:id", :to => :show
95
+ show "/books/:id", :to => :show, :as => :show_book
79
96
 
80
97
  desc "Create a new book"
81
- post "/books", :to => :create
98
+ post "/books", :to => :create, :as => :create_book
82
99
 
83
100
  desc "Update an existing book"
84
- put "/books/:id", :to => :update
101
+ put "/books/:id", :to => :update, :as => :update_book
85
102
 
86
103
  desc "Like a book"
87
- put "/books/:id/like", :to => :like
104
+ put "/books/:id/like", :to => :like, :as => :like_book
105
+ end
106
+
107
+ template do
108
+ title { Smooth.faker('company.catch_phrase') }
109
+ end
110
+
111
+ template :ancient, class: Book do
112
+ year_published { 1776 }
88
113
  end
89
114
 
90
115
  examples :client => :rest do
@@ -0,0 +1,10 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def change
3
+ create_table :users do |t|
4
+ t.string :email
5
+ t.string :role
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class CreateLibraries < ActiveRecord::Migration
2
+ def change
3
+ create_table :libraries do |t|
4
+ t.string :name
5
+ t.string :address
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -11,7 +11,7 @@
11
11
  #
12
12
  # It's strongly recommended that you check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(version: 20140822065916) do
14
+ ActiveRecord::Schema.define(version: 20140824215902) do
15
15
 
16
16
  create_table "authors", force: true do |t|
17
17
  t.string "name"
@@ -27,4 +27,11 @@ ActiveRecord::Schema.define(version: 20140822065916) do
27
27
  t.datetime "updated_at"
28
28
  end
29
29
 
30
+ create_table "users", force: true do |t|
31
+ t.string "email"
32
+ t.string "role"
33
+ t.datetime "created_at"
34
+ t.datetime "updated_at"
35
+ end
36
+
30
37
  end
@@ -0,0 +1,21 @@
1
+ require "spec_helper"
2
+
3
+ describe "Smooth Command Background Job Handling" do
4
+ let(:api) { Smooth() }
5
+
6
+ it "should serialize a command call and restore it from memory" do
7
+ key = api.serialize_for_async('books.create', {title:'New Book'})
8
+ expect(key).to be_present
9
+ end
10
+
11
+ it "should deserialize a command call" do
12
+ key = api.serialize_for_async('books.create', {title:'New Book'})
13
+ hash = Smooth.config.memory_store.read(key).symbolize_keys
14
+ api_name, object_path, payload = hash.values_at(:api, :object_path, :payload)
15
+
16
+ expect(api_name).to eq('My Application')
17
+ expect(Smooth(api_name)).to eq(api)
18
+ expect(Smooth(api_name).lookup(object_path)).to be_present
19
+ expect(payload[:title]).to eq('New Book')
20
+ end
21
+ end
@@ -19,5 +19,13 @@ describe "The Smooth API Definition" do
19
19
  it "should have a version" do
20
20
  expect(api.version).to equal(:v1)
21
21
  end
22
+
23
+ it "should lookup objects by a shortcut alias / path" do
24
+ expect(api.lookup_object_by("books")).to be_a(Smooth::Resource)
25
+ expect(api.lookup_object_by("books.create")).to eq(CreateBook)
26
+ expect(api.lookup_object_by("books.like")).to eq(LikeBook)
27
+ expect(api.lookup_object_by("books.query")).to eq(BookQuery)
28
+ expect(api.lookup_object_by("books.serializer")).to eq(BookSerializer)
29
+ end
22
30
  end
23
31