smooth 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
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