windsor 0.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.
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: []