populate-me 0.0.33 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/LICENSE +1 -1
  4. data/README.md +399 -4
  5. data/Rakefile +14 -0
  6. data/example/config.ru +67 -0
  7. data/lib/populate_me.rb +2 -0
  8. data/lib/populate_me/admin.rb +143 -0
  9. data/lib/populate_me/admin/__assets__/css/main.css +174 -0
  10. data/lib/populate_me/admin/__assets__/js/columnav.js +82 -0
  11. data/lib/populate_me/admin/__assets__/js/main.js +251 -0
  12. data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
  13. data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
  14. data/lib/populate_me/admin/views/page.erb +163 -0
  15. data/lib/populate_me/api.rb +124 -0
  16. data/lib/populate_me/attachment.rb +182 -0
  17. data/lib/populate_me/document.rb +178 -0
  18. data/lib/populate_me/document_mixins/admin_adapter.rb +131 -0
  19. data/lib/populate_me/document_mixins/callbacks.rb +104 -0
  20. data/lib/populate_me/document_mixins/outcasting.rb +49 -0
  21. data/lib/populate_me/document_mixins/persistence.rb +92 -0
  22. data/lib/populate_me/document_mixins/schema.rb +99 -0
  23. data/lib/populate_me/document_mixins/typecasting.rb +60 -0
  24. data/lib/populate_me/document_mixins/validation.rb +44 -0
  25. data/lib/populate_me/file_system_attachment.rb +40 -0
  26. data/lib/populate_me/grid_fs_attachment.rb +127 -0
  27. data/lib/populate_me/mongo.rb +98 -3
  28. data/lib/populate_me/variation.rb +34 -0
  29. data/lib/populate_me/version.rb +4 -0
  30. data/populate-me.gemspec +20 -12
  31. data/test/helper.rb +37 -0
  32. data/test/test_admin.rb +161 -0
  33. data/test/test_api.rb +246 -0
  34. data/test/test_attachment.rb +155 -0
  35. data/test/test_document.rb +120 -0
  36. data/test/test_document_admin_adapter.rb +43 -0
  37. data/test/test_document_callbacks.rb +107 -0
  38. data/test/test_document_persistence.rb +56 -0
  39. data/test/test_document_typecasting.rb +121 -0
  40. data/test/test_mongo.rb +217 -0
  41. data/test/test_variation.rb +91 -0
  42. data/test/test_version.rb +11 -0
  43. metadata +115 -66
  44. data/lib/populate_me/control.rb +0 -196
  45. data/lib/populate_me/control/_public/css/main.css +0 -207
  46. data/lib/populate_me/control/_public/css/plugin.asmselect.css +0 -63
  47. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
  48. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
  49. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_20_555555_1x400.png +0 -0
  50. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_0078a3_1x400.png +0 -0
  51. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
  52. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
  53. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
  54. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png +0 -0
  55. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
  56. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_222222_256x240.png +0 -0
  57. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_4b8e0b_256x240.png +0 -0
  58. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_a83300_256x240.png +0 -0
  59. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_cccccc_256x240.png +0 -0
  60. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_ffffff_256x240.png +0 -0
  61. data/lib/populate_me/control/_public/css/ui-darkness/jquery-ui-1.8.17.custom.css +0 -430
  62. data/lib/populate_me/control/_public/img/grip.png +0 -0
  63. data/lib/populate_me/control/_public/img/icons-cms-solarized.png +0 -0
  64. data/lib/populate_me/control/_public/img/icons-cms.png +0 -0
  65. data/lib/populate_me/control/_public/img/placeholder.png +0 -0
  66. data/lib/populate_me/control/_public/img/placeholder.stash_thumb.gif +0 -0
  67. data/lib/populate_me/control/_public/img/placeholder.stash_thumb.png +0 -0
  68. data/lib/populate_me/control/_public/img/small-loader.gif +0 -0
  69. data/lib/populate_me/control/_public/js/addon.timepicker.js +0 -20
  70. data/lib/populate_me/control/_public/js/jquery-ui.js +0 -114
  71. data/lib/populate_me/control/_public/js/jquery.js +0 -167
  72. data/lib/populate_me/control/_public/js/jquery.mustache.js +0 -559
  73. data/lib/populate_me/control/_public/js/main.js +0 -144
  74. data/lib/populate_me/control/_public/js/plugin.asmselect.js +0 -407
  75. data/lib/populate_me/control/_public/js/plugin.form.js +0 -11
  76. data/lib/populate_me/control/_public/js/plugin.quicksearch.js +0 -1
  77. data/lib/populate_me/control/_public/js/plugin.underwood.js +0 -4
  78. data/lib/populate_me/control/views/populate_me_layout.erb +0 -75
  79. data/lib/populate_me/ext.rb +0 -16
  80. data/lib/populate_me/mongo/backend_api_plug.rb +0 -78
  81. data/lib/populate_me/mongo/crushyform.rb +0 -243
  82. data/lib/populate_me/mongo/mutation.rb +0 -324
  83. data/lib/populate_me/mongo/plug.rb +0 -134
  84. data/lib/populate_me/mongo/stash.rb +0 -171
  85. data/test/spec_ext.rb +0 -29
  86. data/test/spec_mongo_mutation.rb +0 -279
@@ -1,3 +1,98 @@
1
- # encoding: utf-8
2
- require "populate_me/ext"
3
- require 'populate_me/mongo/plug'
1
+ require 'populate_me/document'
2
+ require 'mongo'
3
+
4
+ module PopulateMe
5
+
6
+ class MissingMongoDBError < StandardError; end
7
+
8
+ class Mongo < Document
9
+
10
+ class << self
11
+
12
+ def inherited sub
13
+ super
14
+ sub.set :collection_name, WebUtils.dasherize_class_name(sub.name)
15
+ end
16
+
17
+ def collection
18
+ raise MissingMongoDBError, "Document class #{self.name} does not have a Mongo database." if settings.db.nil?
19
+ settings.db[settings.collection_name]
20
+ end
21
+
22
+ def set_id_field
23
+ field :_id, {type: :id}
24
+ end
25
+
26
+ def sort_by f, direction=1
27
+ direction = 1 if direction==:asc
28
+ direction = -1 if direction==:desc
29
+ if f.is_a?(Hash)
30
+ @current_sort = f
31
+ elsif f.is_a?(Array)
32
+ @current_sort = f.inject({}) do |h,pair|
33
+ h.store(pair[0], pair[1])
34
+ h
35
+ end
36
+ else
37
+ raise(ArgumentError) unless [1,-1].include? direction
38
+ raise(ArgumentError) unless self.new.respond_to? f
39
+ @current_sort = {f => direction}
40
+ end
41
+ self
42
+ end
43
+
44
+ def id_string_key
45
+ (self.fields.keys[0]||'_id').to_s
46
+ end
47
+
48
+ def set_indexes f, ids=[]
49
+ requests = ids.each_with_index.inject([]) do |list, (id, i)|
50
+ list << {update_one:
51
+ {
52
+ filter: {self.id_string_key=>id},
53
+ update: {'$set'=>{f=>i}}
54
+ }
55
+ }
56
+ end
57
+ collection.bulk_write requests
58
+ end
59
+
60
+ def admin_get theid
61
+ theid = BSON::ObjectId.from_string(theid) if BSON::ObjectId.legal?(theid)
62
+ self.cast{ collection.find({id_string_key => theid}).first }
63
+ end
64
+ alias_method :[], :admin_get
65
+
66
+ def admin_find o={}
67
+ query = o.delete(:query) || {}
68
+ o[:sort] ||= @current_sort
69
+ self.cast{ collection.find(query, o) }
70
+ end
71
+
72
+ end
73
+
74
+ attr_accessor :_id
75
+
76
+ def id; @_id; end
77
+ def id= value; @_id = value; end
78
+
79
+ def perform_create
80
+ result = self.class.collection.insert_one(self.to_h)
81
+ if result.ok? and self.id.nil?
82
+ self.id = result.inserted_id
83
+ end
84
+ self.id
85
+ end
86
+
87
+ def perform_update
88
+ self.class.collection.update_one({'_id'=> self.id}, self.to_h)
89
+ self.id
90
+ end
91
+
92
+ def perform_delete
93
+ self.class.collection.delete_one({'_id'=> self.id})
94
+ end
95
+
96
+ end
97
+ end
98
+
@@ -0,0 +1,34 @@
1
+ module PopulateMe
2
+
3
+ class Variation < Struct.new :name, :ext, :job
4
+
5
+ # Simple class to deal with variations of an attachment
6
+ # Mainly variation of images using ImageMagick
7
+ # but it could be anything else like creating the pdf version
8
+ # of a text file
9
+
10
+ def initialize name, ext, job_as_proc=nil, &job_as_block
11
+ super name, ext, job_as_proc||job_as_block
12
+ end
13
+
14
+ class << self
15
+
16
+ def new_image_magick_job name, ext, convert_string, options={}
17
+ o = {
18
+ strip: true, progressive: true,
19
+ }.merge(options)
20
+ defaults = ""
21
+ defaults << "-strip " if o[:strip]
22
+ defaults << "-interlace Plane " if o[:progressive] and [:jpg,:jpeg].include?(ext.to_sym)
23
+ job = lambda{ |src,dst|
24
+ Kernel.system "convert \"#{src}\" #{defaults}#{convert_string} \"#{dst}\""
25
+ }
26
+ self.new name, ext, job
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
@@ -0,0 +1,4 @@
1
+ module PopulateMe
2
+ VERSION = '0.1.0'
3
+ end
4
+
data/populate-me.gemspec CHANGED
@@ -1,24 +1,32 @@
1
+ require File.join(File.dirname(__FILE__), 'lib/populate_me/version')
2
+
1
3
  Gem::Specification.new do |s|
4
+
5
+ s.authors = ["Mickael Riga"]
6
+ s.email = ["mig@mypeplum.com"]
7
+ s.homepage = "https://github.com/mig-hub/populate-me"
8
+ s.licenses = ['MIT']
9
+
2
10
  s.name = 'populate-me'
3
- s.version = '0.0.33'
11
+ s.version = PopulateMe::VERSION
12
+ s.summary = "PopulateMe is an admin system for web applications."
13
+ s.description = "PopulateMe is an admin system for managing structured content of web applications. It is built on top of the Sinatra framework, but can be used along any framework using Rack."
14
+
4
15
  s.platform = Gem::Platform::RUBY
5
- s.summary = "ALPHA !!! Populate Me is relatively complete but simple CMS"
6
- s.description = "ALPHA !!! Populate Me is relatively complete but simple CMS. It includes a Rack middleware for putting in your Rack stack, and a bespoke MongoDB ODM. But Populate Me is not really finished yet."
7
16
  s.files = `git ls-files`.split("\n").sort
8
- s.require_path = './lib'
9
- s.author = "Mickael Riga"
10
- s.email = "mig@mypeplum.com"
11
- s.homepage = "https://github.com/mig-hub/populate-me"
12
- s.licenses = ['MIT']
17
+ s.test_files = s.files.grep(/^test\//)
18
+ s.require_paths = ['lib']
13
19
 
14
- s.add_dependency 'rack-golem', '~> 0'
20
+ s.add_dependency 'web-utils', '~> 0'
21
+ s.add_dependency 'sinatra', '~> 1.0'
15
22
  s.add_dependency 'json', '~> 2.1'
16
- s.add_dependency 'mongo', '~> 2.0'
17
23
 
18
24
  s.add_development_dependency 'bundler', '~> 1.13'
19
- s.add_development_dependency 'bacon', '~> 1.2'
25
+ s.add_development_dependency 'minitest', '~> 5.8'
20
26
  s.add_development_dependency 'rack-test', '~> 0.6'
27
+ s.add_development_dependency 'rack-cerberus', '~> 1.0'
28
+ s.add_development_dependency 'mongo', '~> 2.0'
21
29
  s.add_development_dependency 'racksh', '~> 1.0'
22
-
30
+ s.add_development_dependency 'rake', '~> 10.1'
23
31
  end
24
32
 
data/test/helper.rb ADDED
@@ -0,0 +1,37 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require 'minitest/autorun'
4
+ require 'rack/test'
5
+
6
+ module Minitest::Assertions
7
+ def assert_json response
8
+ assert response.content_type=="application/json", "Expected #{response.inspect} to be a JSON response"
9
+ end
10
+ def assert_for_view json, view_name, title=nil
11
+ assert json['template']==view_name, "Expected #{json.inspect} to have 'template' set to '#{view_name}'"
12
+ unless title.nil?
13
+ assert json['page_title']==title, "Expected #{json.inspect} to have 'page_title' set to '#{title}'"
14
+ end
15
+ end
16
+
17
+ def assert_receive obj, meth, retval=nil, args=[]
18
+ mocked_meth = Minitest::Mock.new
19
+ mocked_meth.expect(:call, retval, args)
20
+ obj.stub meth, mocked_meth do
21
+ yield
22
+ end
23
+ assert mocked_meth.verify, "Expected #{obj.inspect} to receive :#{meth}"
24
+ end
25
+ def refute_receive obj, meth
26
+ proof = nil
27
+ obj.stub meth, proc{proof = :received} do
28
+ yield
29
+ assert(proof!=:received, "Expected #{obj.inspect} not to receive :#{meth}")
30
+ end
31
+ end
32
+ end
33
+
34
+ class Minitest::Spec
35
+ include Rack::Test::Methods
36
+ end
37
+
@@ -0,0 +1,161 @@
1
+ require 'helper'
2
+ require 'populate_me/admin'
3
+
4
+ class Admin < PopulateMe::Admin
5
+ enable :sessions
6
+ set :menu, [
7
+ ['Home Details', '/admin/form/home-details/0'],
8
+ ['Project Page', [
9
+ ['Project Page Intro', '/admin/form/project-page-intro/0'],
10
+ ['Projects', '/admin/list/project'],
11
+ ['Checks', [
12
+ ['Check 1', '/check/1'],
13
+ ['Check 2', '/check/2']
14
+ ]]
15
+ ]]
16
+ ]
17
+ end
18
+
19
+ class AdminWithCerberusPass < Admin
20
+ def self.cerberus_pass
21
+ '123'
22
+ end
23
+ end
24
+
25
+ class AdminCerberusNotAvailable < AdminWithCerberusPass
26
+ def self.cerberus_available?
27
+ false
28
+ end
29
+ end
30
+
31
+ class AdminCerberusDisabled < AdminWithCerberusPass
32
+ disable :cerberus
33
+ end
34
+
35
+ describe PopulateMe::Admin do
36
+
37
+ parallelize_me!
38
+
39
+ let(:app) { ::Admin.new }
40
+
41
+ let(:settings) { app.settings }
42
+
43
+ let(:json) { JSON.parse(last_response.body) }
44
+
45
+ describe 'Settings' do
46
+ it 'Sets paths based on the subclass file path' do
47
+ assert_equal __FILE__, settings.app_file
48
+ end
49
+ it 'Has a default value for the page title tag' do
50
+ assert_equal 'Populate Me', settings.meta_title
51
+ end
52
+ it 'Has a default index_path' do
53
+ assert_equal '/menu', settings.index_path
54
+ end
55
+ it 'Has cerberus enabled by default' do
56
+ assert settings.cerberus?
57
+ end
58
+ describe 'when ENV CERBERUS_PASS is not set' do
59
+ it 'Does not have cerberus_active' do
60
+ refute settings.cerberus_active
61
+ end
62
+ end
63
+ describe 'when ENV CERBERUS_PASS is set' do
64
+ let(:app) { AdminWithCerberusPass.new }
65
+ it 'Has cerberus_active' do
66
+ assert settings.cerberus_active
67
+ end
68
+ end
69
+ describe 'when ENV CERBERUS_PASS is set but gem not loaded' do
70
+ let(:app) { AdminCerberusNotAvailable.new }
71
+ it 'Does not have cerberus_active' do
72
+ refute settings.cerberus_active
73
+ end
74
+ end
75
+ describe 'when ENV CERBERUS_PASS is set but cerberus is disabled' do
76
+ let(:app) { AdminCerberusDisabled.new }
77
+ it 'Does not have cerberus_active' do
78
+ refute settings.cerberus_active
79
+ end
80
+ end
81
+ describe 'when Cerberus is active' do
82
+ let(:app) { AdminWithCerberusPass.new }
83
+ it 'Sets logout_path to /logout' do
84
+ assert_equal '/logout', settings.logout_path
85
+ end
86
+ end
87
+ describe 'when Cerberus is not active' do
88
+ it 'Sets logout_path to false' do
89
+ refute settings.logout_path
90
+ end
91
+ end
92
+ end
93
+
94
+ describe 'Middlewares' do
95
+
96
+ it 'Has API middleware mounted on /api' do
97
+ get '/api'
98
+ assert_predicate last_response, :ok?
99
+ assert_json last_response
100
+ assert json['success']
101
+ end
102
+
103
+ it 'Has assets available on /__assets__' do
104
+ get('/__assets__/css/main.css')
105
+ assert_predicate last_response, :ok?
106
+ assert_equal 'text/css', last_response.content_type
107
+ end
108
+
109
+ describe 'when cerberus is active' do
110
+ let(:app) { AdminWithCerberusPass.new }
111
+ it 'Uses Cerberus for authentication' do
112
+ get '/'
113
+ assert_equal 401, last_response.status
114
+ end
115
+ end
116
+ describe 'when cerberus is inactive' do
117
+ it 'Does not use Cerberus' do
118
+ get '/'
119
+ assert_predicate last_response, :ok?
120
+ end
121
+ end
122
+
123
+ end
124
+
125
+ describe 'Handlers' do
126
+
127
+ describe '/menu' do
128
+
129
+ describe 'when url is root' do
130
+ it 'Returns the correct info' do
131
+ get '/menu'
132
+ assert_predicate last_response, :ok?
133
+ assert_json last_response
134
+ assert_for_view json, 'template_menu', 'Menu'
135
+ assert_equal 2, json['items'].size
136
+ expected_h = {
137
+ 'title'=> 'Home Details', 'href'=> '/admin/form/home-details/0'
138
+ }
139
+ assert_equal expected_h, json['items'][0]
140
+ end
141
+ end
142
+ describe 'when url is nested' do
143
+ it 'Returns the correct info' do
144
+ get '/menu/project-page/checks'
145
+ assert_predicate last_response, :ok?
146
+ assert_json last_response
147
+ assert_for_view json, 'template_menu', 'Checks'
148
+ assert_equal 2, json['items'].size
149
+ expected_h = {
150
+ 'title'=> 'Check 1', 'href'=> '/check/1'
151
+ }
152
+ assert_equal expected_h, json['items'][0]
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ end
159
+
160
+ end
161
+
data/test/test_api.rb ADDED
@@ -0,0 +1,246 @@
1
+ require 'helper'
2
+
3
+ require 'populate_me/document'
4
+ class Band < PopulateMe::Document
5
+ attr_accessor :name, :awsome, :position
6
+ def members; @members ||= []; end
7
+ def validate
8
+ error_on(:name,"WTF") if self.name=='ZZ Top'
9
+ end
10
+ end
11
+ class Band::Member < PopulateMe::Document
12
+ attr_accessor :name
13
+ end
14
+
15
+ require 'populate_me/api'
16
+
17
+ describe 'PopulateMe::API' do
18
+
19
+ # This middleware has the CRUD interface for
20
+ # managing documents through a JSON-based API
21
+ #
22
+ # The API needs the Document class to implement these methods:
23
+ # - Class.admin_get
24
+ # - Needs to be able to accept the ID as a string
25
+ # - The class is responsible for conversion
26
+ # - So a mix of different classes of IDs is not possible
27
+ # - instance.to_h
28
+ # - instance.save
29
+ # - instance.delete
30
+
31
+ parallelize_me!
32
+
33
+ let(:app) {
34
+ PopulateMe::API.new
35
+ }
36
+
37
+ let(:json) {
38
+ JSON.parse(last_response.body)
39
+ }
40
+
41
+ def assert_not_found
42
+ assert_json last_response
43
+ assert_equal 404, last_response.status
44
+ assert_equal 'pass', last_response.headers['X-Cascade']
45
+ refute json['success']
46
+ assert_equal 'Not Found', json['message']
47
+ end
48
+
49
+ def assert_successful_creation
50
+ assert_json last_response
51
+ assert_equal 201, last_response.status
52
+ assert json['success']
53
+ assert_equal 'Created Successfully', json['message']
54
+ end
55
+
56
+ def assert_successful_sorting
57
+ assert_json last_response
58
+ assert_predicate last_response, :ok?
59
+ assert json['success']
60
+ assert_equal 'Sorted Successfully', json['message']
61
+ end
62
+
63
+ def assert_invalid_instance
64
+ assert_json last_response
65
+ assert_equal 400, last_response.status
66
+ refute json['success']
67
+ assert_equal 'Invalid Document', json['message']
68
+ end
69
+
70
+ def assert_successful_instance
71
+ assert_json last_response
72
+ assert_predicate last_response, :ok?
73
+ assert json['success']
74
+ end
75
+
76
+ def assert_successful_update
77
+ assert_json last_response
78
+ assert_predicate last_response, :ok?
79
+ assert json['success']
80
+ assert_equal 'Updated Successfully', json['message']
81
+ end
82
+
83
+ def assert_successful_deletion
84
+ assert_json last_response
85
+ assert_predicate last_response, :ok?
86
+ assert json['success']
87
+ assert_equal 'Deleted Successfully', json['message']
88
+ end
89
+
90
+ describe 'GET /version' do
91
+ it 'Returns the PopulateMe version' do
92
+ get('/version')
93
+ assert_json last_response
94
+ assert_predicate last_response, :ok?
95
+ assert json['success']
96
+ assert_equal PopulateMe::VERSION, json['version']
97
+ end
98
+ end
99
+
100
+ describe 'POST /:model' do
101
+
102
+ it 'Creates successfully' do
103
+ post('/band', {data: {id: 'neurosis', name: 'Neurosis'}})
104
+ assert_successful_creation
105
+ assert_equal 'Neurosis', json['data']['name']
106
+ end
107
+
108
+ it 'Typecasts before creating' do
109
+ post('/band', {data: {name: 'Arcade Fire', awsome: 'true'}})
110
+ assert_successful_creation
111
+ assert json['data']['awsome']
112
+ end
113
+
114
+ it 'Can create a doc even if no data is sent' do
115
+ post '/band'
116
+ assert_successful_creation
117
+ end
118
+
119
+ it 'Fails if the doc is invalid' do
120
+ post('/band', {data: {id: 'invalid_doc_post', name: 'ZZ Top'}})
121
+ assert_invalid_instance
122
+ assert_equal({'name'=>['WTF']}, json['data'])
123
+ assert_nil Band.admin_get('invalid_doc_post')
124
+ end
125
+
126
+ it 'Redirects if destination is given' do
127
+ post '/band', {'_destination'=>'http://example.org/anywhere'}
128
+ assert_equal 302, last_response.status
129
+ assert_equal 'http://example.org/anywhere', last_response.header['Location']
130
+ end
131
+
132
+ end
133
+
134
+ describe 'PUT /:model' do
135
+
136
+ it 'Can set indexes for sorting' do
137
+ post('/band', {data: {id: 'sortable1', name: 'Sortable 1'}})
138
+ post('/band', {data: {id: 'sortable2', name: 'Sortable 2'}})
139
+ post('/band', {data: {id: 'sortable3', name: 'Sortable 3'}})
140
+ put '/band', {
141
+ 'action'=>'sort',
142
+ 'field'=>'position',
143
+ 'ids'=> ['sortable2','sortable3','sortable1']
144
+ }
145
+ assert_successful_sorting
146
+ assert_equal 0, Band.admin_get('sortable2').position
147
+ assert_equal 1, Band.admin_get('sortable3').position
148
+ assert_equal 2, Band.admin_get('sortable1').position
149
+ end
150
+
151
+ it 'Redirects after sorting if destination is given' do
152
+ post('/band', {data: {id: 'redirectsortable1', name: 'Redirect Sortable 1'}})
153
+ post('/band', {data: {id: 'redirectsortable2', name: 'Redirect Sortable 2'}})
154
+ post('/band', {data: {id: 'redirectsortable3', name: 'Redirect Sortable 3'}})
155
+ put '/band', {
156
+ 'action'=>'sort',
157
+ 'field'=>'position',
158
+ 'ids'=> ['redirectsortable2','redirectsortable3','redirectsortable1'],
159
+ '_destination'=>'http://example.org/anywhere'
160
+ }
161
+ assert_equal 302, last_response.status
162
+ assert_equal 'http://example.org/anywhere', last_response.header['Location']
163
+ end
164
+
165
+ end
166
+
167
+ describe 'GET /:model/:id' do
168
+ it 'Sends a not-found when the model is not a class' do
169
+ get('/wizz/42')
170
+ assert_not_found
171
+ end
172
+ it 'Sends not-found when the model is a class but not a model' do
173
+ get('/string/42')
174
+ assert_not_found
175
+ end
176
+ it 'Sends not-found when the id is not provided' do
177
+ get('/band/')
178
+ assert_not_found
179
+ end
180
+ it 'Sends not-found when the instance does not exist' do
181
+ get('/band/666')
182
+ assert_not_found
183
+ end
184
+ it 'Sends the instance if it exists' do
185
+ post('/band', {data: {id: 'sendable', name: 'Morphine'}})
186
+ get('/band/sendable')
187
+ assert_successful_instance
188
+ assert_equal Band.admin_get('sendable').to_h, json['data']
189
+ end
190
+ end
191
+
192
+ describe 'PUT /:model/:id' do
193
+ it 'Sends not-found if the instance does not exist' do
194
+ put('/band/666')
195
+ assert_not_found
196
+ end
197
+ it 'Fails if the document is invalid' do
198
+ post('/band', {data: {id: 'invalid_doc_put', name: 'Valid here'}})
199
+ put('/band/invalid_doc_put', {data: {name: 'ZZ Top'}})
200
+ assert_invalid_instance
201
+ assert_equal({'name'=>['WTF']}, json['data'])
202
+ refute_equal 'ZZ Top', Band.admin_get('invalid_doc_put').name
203
+ end
204
+ it 'Updates documents' do
205
+ post('/band', {data: {id: 'updatable', name: 'Updatable'}})
206
+ put('/band/updatable', {data: {awsome: 'yes'}})
207
+ assert_successful_update
208
+ obj = Band.admin_get('updatable')
209
+ assert_equal 'yes', obj.awsome
210
+ assert_equal 'Updatable', obj.name
211
+ end
212
+ # it 'Updates nested documents' do
213
+ # obj = Band.admin_get('3')
214
+ # put('/band/3', {data: {members: [
215
+ # {id: obj.members[0].id, _class: 'Band::Member', name: 'Joey Ramone'},
216
+ # {id: obj.members[1].id, _class: 'Band::Member'},
217
+ # ]}})
218
+ # assert_successful_update
219
+ # obj = Band.admin_get('3')
220
+ # assert_equal 'yes', obj.awsome
221
+ # assert_equal 'The Ramones', obj.name
222
+ # assert_equal 2, obj.members.size
223
+ # assert_equal 'Joey Ramone', obj.members[0].name
224
+ # assert_equal 'Deedee Ramone', obj.members[1].name
225
+ # end
226
+ end
227
+
228
+ describe 'DELETE /:model/:id' do
229
+ it 'Sends not-found if the instance does not exist' do
230
+ delete('/band/666')
231
+ assert_not_found
232
+ end
233
+ it 'Returns a deletion response when the instance exists' do
234
+ post('/band', {data: {id: 'deletable', name: '1D'}})
235
+ delete('/band/deletable')
236
+ assert_successful_deletion
237
+ assert_instance_of Hash, json['data']
238
+ end
239
+ it 'Redirects if destination is given' do
240
+ delete('/band/2', {'_destination'=>'http://example.org/anywhere'})
241
+ assert_equal 302, last_response.status
242
+ assert_equal 'http://example.org/anywhere', last_response.header['Location']
243
+ end
244
+ end
245
+ end
246
+