windsor 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2011 ExactTarget, http://www.exacttarget.com/
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = Windsor
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Windsor'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task :default => :test
@@ -0,0 +1,271 @@
1
+ class WindsorController < ApplicationController
2
+ rescue_from ActiveRecord::RecordNotFound, :with => :resource_not_found
3
+ rescue_from JSON::ParserError, :with => :unsupported_media_type
4
+
5
+ before_filter :get_resource, :except => [:index, :create]
6
+ before_filter :scope, :set_attributes, :check_implemented
7
+
8
+ attr_accessor :exceptional_attributes
9
+ attr_accessor :attributes_all_or_none
10
+
11
+ def index
12
+ @max_page_size = 100 unless @max_page_size
13
+ if params[:page].nil?
14
+ current_page_index = 0
15
+ offset = 0
16
+ else
17
+ current_page_index = params[:page].to_i - 1
18
+ offset = @max_page_size * current_page_index
19
+ end
20
+ items = model_class.where(scope).limit(@max_page_size).offset(offset).all
21
+ total_items = model_class.count
22
+
23
+ object = {
24
+ get_controller_name => items,
25
+ :pagination => get_pagination_object(total_items, current_page_index)
26
+ }
27
+ render_json object
28
+ end
29
+
30
+ def create
31
+ request_body = JSON.parse(request.body.read)
32
+ existing_attributes = enabled_attributes(hashity_hash(model_class.new)).map { |item| item.to_s }
33
+ request_body = prune_extra_attributes(request_body, existing_attributes)
34
+
35
+ model_object = model_class.new(request_body.merge(scope))
36
+ if model_object.save
37
+ headers["Location"] = get_self_link({"id" => model_object.id})
38
+ render_json model_object, :created
39
+ else
40
+ render_error 422, "InvalidResourceFields", "Some fields couldn't be validated.", model_object.errors.messages
41
+ end
42
+ end
43
+
44
+ def show
45
+ render_json @model_object
46
+ end
47
+
48
+ def update
49
+ request_body = JSON.parse(request.body.read)
50
+ existing_attributes = enabled_attributes(hashity_hash(@model_object)).map { |item| item.to_s }
51
+ request_body = prune_extra_attributes(request_body, existing_attributes)
52
+
53
+ # Checks that there is an incoming attribute for every existing attribute (i.e. no partial updates)
54
+ missing_attributes = []
55
+ existing_attributes.each do |existing_attribute, existing_value|
56
+ unless request_body.include?(existing_attribute)
57
+ missing_attributes << existing_attribute
58
+ end
59
+ end
60
+
61
+ unless missing_attributes.empty?
62
+ errors = missing_attributes.map { |missing_attribute| { missing_attribute => "must be present" } }
63
+ render_error 422, "InvalidResourceFields", "Some fields couldn't be validated.", errors
64
+ return
65
+ end
66
+
67
+ # Compare the existing attributes and the passed-in attributes and remove any passed-in attributes with
68
+ # a value identical to the corresponding existing attribute. This is necessary because update_attributes
69
+ # will error out when trying to update an existing attribute with the same value, if that attribute has a
70
+ # uniqueness validation on it.
71
+ existing_attributes.each do |existing_attribute, existing_value|
72
+ if request_body[existing_attribute] == existing_value
73
+ request_body.delete existing_attribute
74
+ end
75
+ end
76
+
77
+ if @model_object.update_attributes(request_body)
78
+ render_json @model_object
79
+ else
80
+ render_error 422, "InvalidResourceFields", "Some fields couldn't be validated.", @model_object.errors.messages
81
+ end
82
+ end
83
+
84
+ def destroy
85
+ @model_object.destroy
86
+ render :nothing => true, :status => 204
87
+ end
88
+
89
+ def to_representation(object)
90
+ return object
91
+ end
92
+
93
+ # Called before every action, if you want to set the attributes of the representation
94
+ # redefine this function in your controller with a call to attributes and your paramaters.
95
+ # Example:
96
+ # def set_attributes
97
+ # attributes :all, :except => [:name]
98
+ # end
99
+ def set_attributes
100
+ attributes :all
101
+ end
102
+
103
+ def attributes(all_or_none, exceptional_attributes = { :except => [] })
104
+ @attributes_all_or_none = all_or_none
105
+ @exceptional_attributes = exceptional_attributes[:except].map! { |ex_attr| ex_attr.to_sym }
106
+ end
107
+
108
+ def enabled_attributes(object_hash)
109
+ @exceptional_attributes = Array(@exceptional_attributes)
110
+ raise ArgumentError unless [:all, :none].include?(@attributes_all_or_none)
111
+
112
+ if @attributes_all_or_none == :all
113
+ keys = object_hash.keys
114
+ #keys.map! { |key| key.to_sym }
115
+ keys - @exceptional_attributes.map { |enabled_attr| enabled_attr.to_s }
116
+ else
117
+ @exceptional_attributes.map { |enabled_attr| enabled_attr.to_s }
118
+ end
119
+ end
120
+
121
+ # Called before every action, if you want to disable certain actions redefine
122
+ # this function in your controller with a call to actions and your paramaters.
123
+ # Example:
124
+ # def set_actions
125
+ # actions :all, :except => [:destroy]
126
+ # end
127
+ def set_actions
128
+ actions :all
129
+ end
130
+
131
+ def actions(all_or_none, exceptional_actions = { :except => [] })
132
+ @actions_all_or_none = all_or_none
133
+ @exceptional_actions = exceptional_actions[:except]
134
+ end
135
+
136
+ def enabled_actions
137
+ @actions_all_or_none ||= :all
138
+ @exceptional_actions ||= []
139
+ @exceptional_actions = Array(@exceptional_actions)
140
+ raise ArgumentError unless [:all, :none].include?(@actions_all_or_none)
141
+
142
+ if @actions_all_or_none == :all
143
+ [:index, :show, :create, :update, :destroy] - @exceptional_actions
144
+ else
145
+ @exceptional_actions
146
+ end
147
+ end
148
+
149
+ def get_self_link(object_hash)
150
+ url_conditions = { :controller => get_controller_name, :action => "index" }
151
+ url_conditions.merge!(:action => "show", :id => object_hash["id"].to_s) unless object_hash["id"].nil?
152
+ url_for(url_conditions)
153
+ end
154
+
155
+ def get_controller_name
156
+ model_class.name.underscore.pluralize
157
+ end
158
+
159
+ private
160
+
161
+ def get_pagination_object(total_items, current_page_index)
162
+ total_pages = (total_items.to_f / @max_page_size).ceil
163
+ total_pages = 1 if total_pages == 0 # Collections with no items in them still have 1 page.
164
+ last_page_index = total_pages - 1
165
+ query_parameters = request.query_parameters
166
+ pagination = {
167
+ :total_items => total_items,
168
+ :max_page_size => @max_page_size,
169
+ :first => page_link(1),
170
+ :last => page_link(total_pages.to_s)
171
+ }
172
+ unless current_page_index == last_page_index
173
+ pagination[:next] = page_link((current_page_index + 2))
174
+ end
175
+ if current_page_index >= 1
176
+ pagination[:previous] = page_link(current_page_index)
177
+ end
178
+ return pagination
179
+ end
180
+
181
+ def page_link(page_number)
182
+ query_parameters = request.query_parameters
183
+ base_url = request.base_url + request.path
184
+ query_parameters[:page] = page_number
185
+ return base_url + "?" + query_parameters.to_query
186
+ end
187
+
188
+ # Removes extra attributes passed in. Extra attributes is defined as attributes not sent in a GET.
189
+ def prune_extra_attributes(request_body, existing_attributes)
190
+ request_body.each do |request_attribute, request_value|
191
+ request_body.delete request_attribute unless existing_attributes.include?(request_attribute)
192
+ end
193
+ return request_body
194
+ end
195
+
196
+ def model_class
197
+ Kernel.const_get(self.controller_name.singularize.camelize )
198
+ end
199
+
200
+ def get_resource
201
+ @model_object = model_class.where(scope).find(params[:id])
202
+ end
203
+
204
+ def check_implemented
205
+ set_actions
206
+ unless enabled_actions.include?(params[:action].to_sym)
207
+ render_error(405, "MethodNotImplemented", "Method not implemented.", request.method.to_s.upcase)
208
+ end
209
+ end
210
+
211
+ def scope
212
+ {}
213
+ end
214
+
215
+ def prepare_representation(object)
216
+ object.merge!(:self => get_self_link(object))
217
+ attributes = enabled_attributes(object)
218
+ object.each do |key, value|
219
+ object.delete key unless attributes.include?(key)
220
+ end
221
+ to_representation(object)
222
+ end
223
+
224
+ def hashity_hash(item)
225
+ begin
226
+ return item.attributes
227
+ rescue NoMethodError
228
+ begin
229
+ return item.to_hash
230
+ rescue NoMethodError
231
+ raise ArgumentError, "Your object could not be converted to a hash."
232
+ end
233
+ end
234
+ end
235
+
236
+ def render_json(object, status = 200)
237
+ begin
238
+ list_name = get_controller_name
239
+ if !object[list_name].nil? && object[list_name].is_a?(Array)
240
+ object[list_name].map! do |item|
241
+ prepare_representation(hashity_hash(item))
242
+ end
243
+ object[:self] = get_self_link(object)
244
+ else
245
+ object = prepare_representation(hashity_hash(object))
246
+ end
247
+ render :json => object, :status => status
248
+ rescue ArgumentError
249
+ if Rails.env.production? || Rails.env.test?
250
+ render_error(500, 'CouldNotRenderJSON', 'Could not render JSON')
251
+ else
252
+ render_error(500, 'CouldNotRenderJSON', 'Could not render JSON', object.inspect)
253
+ end
254
+ end
255
+ end
256
+
257
+ def render_error(status, type, message = "", detail = {})
258
+ raise ArgumentError unless status && type
259
+ raise ArgumentError unless message.is_a?(String)
260
+ render :json => { :error => { :type => type.to_s, :message => message, :detail => detail } }, :status => status
261
+ end
262
+
263
+
264
+ def resource_not_found
265
+ render_error(404, "ResourceNotFound", "Resource not found.")
266
+ end
267
+
268
+ def unsupported_media_type
269
+ render_error(415, "InvalidJSON", "Invalid JSON.", request.body.read)
270
+ end
271
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :windsor do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,9 @@
1
+ require 'rails'
2
+ require 'windsor'
3
+
4
+ module Windsor
5
+ class Engine < Rails::Engine
6
+ engine_name :windsor
7
+ #paths.app.controllers = "app/controllers"
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Windsor
2
+ VERSION = "0.0.2"
3
+ end
data/lib/windsor.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'windsor/rails' if defined?(Rails)
2
+
3
+ module Windsor
4
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: windsor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sam DeCesare
9
+ - Gregg Caines
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2011-09-27 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ requirement: &2156447380 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 3.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2156447380
26
+ - !ruby/object:Gem::Dependency
27
+ name: sqlite3
28
+ requirement: &2156446960 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *2156446960
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ requirement: &2156446500 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2156446500
48
+ description: Windsor is a plugin for building RESTful APIs in Rails.
49
+ email:
50
+ - sam@samdecesare.com
51
+ - gregg@caines.ca
52
+ executables: []
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - app/controllers/windsor_controller.rb
57
+ - lib/tasks/windsor_tasks.rake
58
+ - lib/windsor/rails.rb
59
+ - lib/windsor/version.rb
60
+ - lib/windsor.rb
61
+ - LICENSE
62
+ - Rakefile
63
+ - README.rdoc
64
+ homepage: http://windsorapi.org
65
+ licenses: []
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.10
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Windsor is a plugin for building RESTful APIs in Rails.
88
+ test_files: []