gizmo-codescratch 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,22 @@
1
+ Copyright (c) 2012 Craig Wickesser
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ gizmo [![Build Status](https://secure.travis-ci.org/codescratch/gizmo.png?branch=master)](http://travis-ci.org/codescratch/gizmo)
2
+ =====
3
+
4
+ Removing the crud from developing an API to support CRUD, in the name of Gizmo.
5
+
6
+ ## Installation
7
+
8
+ The preferred method for installing Gizmo is with bundler. Simply add Gizmo to your `Gemfile`:
9
+
10
+ gem "gizmo"
11
+
12
+ ## Usage
13
+
14
+ ```ruby
15
+ class PostsController < ApplicationController
16
+ include Gizmo
17
+
18
+ # for a simple HTML request
19
+ def index
20
+ gizmo_response = find_all Post
21
+ @posts = gizmo_response.data
22
+ end
23
+
24
+ # for a JSON request
25
+ def index
26
+ render find_all(Post.order_by([[:updated_at, :desc]])).to_hash
27
+ end
28
+
29
+ def show
30
+ render find(Post, params[:id]).to_hash
31
+ end
32
+
33
+ def create
34
+ render make(Post, params[:post]).to_hash
35
+ end
36
+
37
+ def update
38
+ render modify(CmsSupport::Post, params[:id], params[:post]).to_hash
39
+ end
40
+
41
+ def destroy
42
+ render delete(CmsSupport::Post, params[:id]).to_hash
43
+ end
44
+ end
45
+ ```
46
+
47
+ ## TODO
48
+
49
+ * look for TODO's
50
+ * abtract persistence layer to suport Mongoid, ActiveRecord, etc
51
+ * error handling :)
52
+ * The gizmo more or less expects to be included into a Rails controller.
data/lib/gizmo.rb ADDED
@@ -0,0 +1,100 @@
1
+ require 'logger'
2
+ require 'mongoid'
3
+
4
+ require "gizmo/version"
5
+ require "gizmo/models"
6
+ require "gizmo/base_operation"
7
+ require "gizmo/find_all"
8
+ require "gizmo/find"
9
+ require "gizmo/search"
10
+ require "gizmo/make"
11
+ require "gizmo/modify"
12
+ require "gizmo/delete"
13
+ require "gizmo/aop"
14
+
15
+ module Gizmo
16
+ @logger = Logger.new STDERR
17
+
18
+ class << self
19
+ attr_accessor :logger
20
+ end
21
+
22
+ # Find all items
23
+ #
24
+ # @param [Mongoid::Criteria] criteria
25
+ #
26
+ # @return [Gizmo::Response]
27
+ def find_all(*args)
28
+ context = create_context
29
+ operation = FindAll.new(context)
30
+ response = operation.call(*args)
31
+ operation.set_response_headers response
32
+ response
33
+ end
34
+
35
+ # Find an item
36
+ #
37
+ # @param [Mongoid::Criteria] criteria
38
+ # @param [String, Moped::BSON::ObjecId] id
39
+ #
40
+ # @return [Gizmo::Response]
41
+ def find(*args)
42
+ context = create_context
43
+ Find.new(context).call(*args)
44
+ end
45
+
46
+ def search(*args)
47
+ context = create_context
48
+ Search.new(context).call(*args)
49
+ end
50
+
51
+ # Make a new instance of an item
52
+ #
53
+ # @param [Mongoid::Criteria] criteria
54
+ # @param [Hash] attrs
55
+ #
56
+ # @return [Gizmo::Response]
57
+ def make(*args)
58
+ context = create_context
59
+ operation = Make.new(context)
60
+ response = operation.call(*args)
61
+ operation.set_response_headers response
62
+ response
63
+ end
64
+
65
+ # Modify an item
66
+ #
67
+ # @param [Mongoid::Criteria] criteria
68
+ # @param [String, Moped::BSON::ObjecId] id
69
+ # @param [Hash] attrs
70
+ #
71
+ # @return [Gizmo::Response]
72
+ def modify(*args)
73
+ context = create_context
74
+ Modify.new(context).call(*args)
75
+ end
76
+
77
+ # Delete an item
78
+ #
79
+ # @param [Mongoid::Criteria] criteria
80
+ # @param [String, Moped::BSON::ObjecId] id
81
+ #
82
+ # @return [Gizmo::Response]
83
+ def delete(*args)
84
+ context = create_context
85
+ Delete.new(context).call(*args)
86
+ end
87
+
88
+ private
89
+
90
+ def create_context
91
+ if defined?(ActionController) && self.kind_of?(ActionController::Base)
92
+ Context.new(gizmo: self, format: request.format.to_sym, callback: params[:callback])
93
+ else
94
+ Context.new(gizmo: self, format: :json, callback: nil)
95
+ end
96
+ end
97
+
98
+
99
+ end
100
+
data/lib/gizmo/aop.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Gizmo
2
+ require 'gizmo/aop/exception_handling'
3
+ end
@@ -0,0 +1,44 @@
1
+ require 'aquarium'
2
+
3
+ module Gizmo
4
+ class ExceptionHandling
5
+ include Aquarium::DSL
6
+
7
+ def self.log_error(error)
8
+ puts "ERROR:: #{error.message}: #{error.backtrace}"
9
+
10
+ if defined?(Rails) && Rails.logger
11
+ Rails.logger.error "#{error.message}: #{error.backtrace}"
12
+ end
13
+ end
14
+
15
+ #around :calls_to => [:find_all, :find, :make, :modify, :delete], :in_types => /Controller$/ do |jp, obj, *args|
16
+ types = [Gizmo::FindAll, Gizmo::Find, Gizmo::Make, Gizmo::Modify, Gizmo::Delete]
17
+ around :calls_to => [:call], :in_types => types do |jp, operation, *args|
18
+ production = ENV['RAILS_ENV'] == 'production'
19
+
20
+ begin
21
+ response = jp.proceed
22
+ rescue Mongoid::Errors::DocumentNotFound => ex
23
+ err = Error.new("Unable to locate #{args.first} with id=#{args[1]}", production ? [] : ex.backtrace.slice(0, 10))
24
+ log_error err
25
+ response = operation.create_response
26
+ response.status = 404
27
+ response.data = err
28
+ rescue Mongoid::Errors::Validations => ex
29
+ err = ValidationError.new("Validation failed for one or more fields", ex.document.errors)
30
+ log_error err
31
+ response = operation.create_response
32
+ response.status = 422
33
+ response.data = err
34
+ rescue Exception => ex
35
+ err = Error.new("[test] Internal server error occurred: #{ex.message}", production ? [] : ex.backtrace.slice(0, 10))
36
+ log_error err
37
+ response = operation.create_response
38
+ response.status = 500
39
+ response.data = err
40
+ end
41
+ response
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ module Gizmo
2
+ class BaseOperation
3
+
4
+ attr_reader :context
5
+
6
+ # Creates a new Find and sets its context
7
+ #
8
+ # @param [Gizmo::Context] context
9
+ def initialize(context)
10
+ @context = context
11
+ end
12
+
13
+ def create_response
14
+ Response.new(format: context.format, callback: context.callback, status: default_status)
15
+ end
16
+
17
+ def default_status
18
+ 200
19
+ end
20
+
21
+ # Set headers on the response
22
+ #
23
+ # @param [Gizmo::Response] gizmo_response a response used to set headers on the HTTP response
24
+ def set_response_headers(gizmo_response)
25
+ if context.gizmo.respond_to?(:headers) && gizmo_response.headers
26
+ gizmo_response.headers.each do |name, value|
27
+ context.gizmo.headers[name] = value
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ module Gizmo
2
+ class Delete < BaseOperation
3
+
4
+ # Delete a single item using the provided criteria.
5
+ #
6
+ # @param [Mongoid::Criteria] criteria the criteria to use for deleting a single item
7
+ # @param [String, Moped::BSON::ObjectId] id the ID of the item being deleted
8
+ #
9
+ # @return [Gizmo::Response]
10
+ def call(criteria, id)
11
+ response = create_response
12
+ criteria.find(id).delete
13
+ response.data = {}
14
+ response
15
+ end
16
+ end
17
+ end
data/lib/gizmo/find.rb ADDED
@@ -0,0 +1,23 @@
1
+ module Gizmo
2
+ class Find < BaseOperation
3
+
4
+ # Find a single item using the provided criteria.
5
+ #
6
+ # @param [Mongoid::Criteria] criteria the criteria to use for finding a single item
7
+ # @param [String, Moped::BSON::ObjectId] id the ID of the item being found
8
+ #
9
+ # @return [Gizmo::Response]
10
+ def call(criteria, id=nil)
11
+ response = create_response
12
+ if criteria.is_a? Mongoid::Criteria
13
+ response.data = criteria.where(:id=>id).first
14
+ elsif !criteria.respond_to?(:find) && id.nil?
15
+ response.data = criteria
16
+ else
17
+ response.data = criteria.find id
18
+ end
19
+ raise Mongoid::Errors::DocumentNotFound.new(criteria.klass, :id=>id) if response.data.nil?
20
+ response
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Gizmo
2
+ class FindAll < BaseOperation
3
+
4
+ # Find all items using the provided criteria.
5
+ #
6
+ # @param [Mongoid::Criteria] criteria the criteria to use for finding all items
7
+ #
8
+ # @return [Gizmo::Response]
9
+ def call(criteria)
10
+ response = create_response
11
+ all_items = criteria.all
12
+ response.header 'X-Total-Results', all_items.count
13
+ response.data = all_items
14
+ response
15
+ end
16
+ end
17
+ end
data/lib/gizmo/make.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Gizmo
2
+ class Make < BaseOperation
3
+
4
+
5
+ # Make a single item using the provided criteria.
6
+ #
7
+ # @param [Mongoid::Criteria] criteria the criteria to use for making a single item
8
+ # @param [Hash] attrs the attributes used to make a new instance
9
+ # @param [Proc] location_proc a Proc that will be invoked with the ID of the newly
10
+ # created item, defaults to nil.
11
+ #
12
+ # @return [Gizmo::Response]
13
+ def call(criteria, attrs, location_proc=nil)
14
+
15
+ response = create_response
16
+ item = criteria.create!(attrs)
17
+ response.data = item
18
+
19
+ if context.gizmo.respond_to?(:url_for)
20
+ if location_proc.nil?
21
+ url = context.gizmo.url_for item
22
+ else
23
+ url = location_proc.call(item.id)
24
+ end
25
+
26
+ response.header :Location, url unless url.nil?
27
+ end
28
+ response
29
+ end
30
+
31
+ def default_status
32
+ 201
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,6 @@
1
+ module Gizmo
2
+ require 'gizmo/models/response'
3
+ require 'gizmo/models/context'
4
+ require 'gizmo/models/error'
5
+ require 'gizmo/models/validation_error'
6
+ end
@@ -0,0 +1,15 @@
1
+ module Gizmo
2
+ # Provides context when performing an operation (i.e. find_all, find)
3
+ class Context
4
+
5
+ attr_accessor :format, :callback, :gizmo
6
+
7
+ # TODO document options
8
+ def initialize(attrs={})
9
+ attrs.each do |key, val|
10
+ setter = :"#{key}="
11
+ send setter, val if respond_to?(setter)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Gizmo
2
+ class Error
3
+ attr_reader :message, :backtrace
4
+
5
+ def initialize(message, backtrace=nil)
6
+ @message = message
7
+ @backtrace = backtrace
8
+ end
9
+
10
+ def as_json(opts={})
11
+ {message: message, backtrace: backtrace}
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ module Gizmo
2
+ class Response
3
+
4
+ attr_accessor :format, :data, :callback, :status, :headers
5
+
6
+ # TODO document options
7
+ def initialize(attrs={})
8
+ @headers = {}
9
+
10
+ attrs.each do |name, val|
11
+ setter = :"#{name}="
12
+ send(setter, val) if respond_to? setter
13
+ end
14
+ end
15
+
16
+ def header(name, value)
17
+ headers[name.to_s] = value.to_s
18
+ end
19
+
20
+ def to_hash(opts={})
21
+ hash = {
22
+ format => data.as_json(opts),
23
+ status: status,
24
+ callback: callback
25
+ }
26
+ hash[:headers] = headers unless headers.nil? || headers.empty?
27
+ hash
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ module Gizmo
2
+ class ValidationError < Error
3
+ attr_reader :errors
4
+
5
+ def initialize(message, errors, backtrace=nil)
6
+ @message = message
7
+ @errors = errors
8
+ @backtrace = backtrace
9
+ end
10
+
11
+ def as_json(opts={})
12
+ {message: message, errors: errors, backtrace: backtrace}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ module Gizmo
2
+ class Modify < BaseOperation
3
+
4
+ # Modify a single item using the provided criteria.
5
+ #
6
+ # @param [Mongoid::Criteria] criteria the criteria to use for finding the item to update
7
+ # @param [String, Moped::BSON::ObjectId] id the ID of the item being updated
8
+ # @param [Hash] attrs the attributes used to update an existing instance
9
+ # @param [Proc] update_strategy The default update strategy
10
+ # does a mass update of attributes, which is not always ideal. A block can
11
+ # be given which will be passed two parameters: the item to update and the attributes.
12
+ # The block must return the data it wishes to send back in the response.
13
+ #
14
+ # @return [Gizmo::Response]
15
+ def call(criteria, id, attrs, update_strategy=nil)
16
+ attrs.delete '_id'
17
+
18
+ response = create_response
19
+ #TODO: refactor this pattern
20
+ if criteria.is_a? Mongoid::Criteria
21
+ item = criteria.where(:id=>id).first
22
+ raise Mongoid::Errors::DocumentNotFound.new(criteria.klass, :id=>id) if item.nil?
23
+ else
24
+ item = criteria.find id
25
+ end
26
+
27
+ if update_strategy.nil?
28
+ item.update_attributes!(attrs)
29
+ else
30
+ item = update_strategy.call item, attrs
31
+ end
32
+ response.data = item
33
+ response
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module Gizmo
2
+ class Search < BaseOperation
3
+
4
+ # Search for items.
5
+ #
6
+ # @param [Mongoid::Criteria] criteria the criteria to use for searching the database
7
+ # @param [Hash] params parameters used for searching
8
+ # @option params [String] :term the field to search
9
+ # @option params [String] :query the value used for searching
10
+ # @option params [Integer] :num_items the maximum number of items to return, default 10
11
+ # @option params [Boolean] :fuzzy true if fuzzy matching should be used, false otherwise, default to false
12
+ # @param [Proc] if a block is given, it will be passed the criteria and params. It should
13
+ # return the result/data. note: the block will be used to perform the search.
14
+ #
15
+ # @return [Gizmo::Response]
16
+ def call(criteria, params={})
17
+ response = create_response
18
+ if block_given?
19
+ response.data = yield criteria, params
20
+ else
21
+ term = params[:term]
22
+ query = params[:query]
23
+ num_items = (params[:num_items] || 10).to_i
24
+ num_items = 10 if num_items > 100 || num_items < 1
25
+
26
+ fuzzy = !!((params[:fuzzy] || "") =~ /true/i)
27
+ if fuzzy
28
+ query = /#{query}/i
29
+ end
30
+
31
+ response.data = criteria.where(term => query).limit(num_items)
32
+ end
33
+ response
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module Gizmo
2
+ VERSION = "0.0.2" unless defined?(Gizmo::VERSION)
3
+ end
@@ -0,0 +1,12 @@
1
+ class FutzController
2
+ include Gizmo
3
+ attr_accessor :headers
4
+
5
+ def initialize
6
+ @headers = {}
7
+ end
8
+
9
+ def url_for(obj)
10
+ "http://localhost:3000/posts/#{obj.id.to_s}"
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module ModelsForTesting
2
+ class Post
3
+ include Mongoid::Document
4
+
5
+ field :title, type: String
6
+ field :content, type: String
7
+ has_many :comments
8
+ validates_presence_of :title
9
+ end
10
+
11
+ class Comment
12
+ include Mongoid::Document
13
+ field :message, type: String
14
+ field :author, type: String
15
+ belongs_to :post
16
+
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: gizmo_test
5
+ hosts:
6
+ - localhost:27017
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gizmo-codescratch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Craig Wickesser
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mongoid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: aquarium
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.5.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.5.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: database_cleaner
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: json
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: Removing the crud from developing an API to support CRUD, in the name
111
+ of Gizmo.
112
+ email:
113
+ - craig@codescratch.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - LICENSE
119
+ - README.md
120
+ - lib/gizmo/aop/exception_handling.rb
121
+ - lib/gizmo/aop.rb
122
+ - lib/gizmo/base_operation.rb
123
+ - lib/gizmo/delete.rb
124
+ - lib/gizmo/find.rb
125
+ - lib/gizmo/find_all.rb
126
+ - lib/gizmo/make.rb
127
+ - lib/gizmo/models/context.rb
128
+ - lib/gizmo/models/error.rb
129
+ - lib/gizmo/models/response.rb
130
+ - lib/gizmo/models/validation_error.rb
131
+ - lib/gizmo/models.rb
132
+ - lib/gizmo/modify.rb
133
+ - lib/gizmo/search.rb
134
+ - lib/gizmo/version.rb
135
+ - lib/gizmo.rb
136
+ - spec/support/futz_controller.rb
137
+ - spec/support/models_for_testing.rb
138
+ - spec/support/mongoid.yml
139
+ homepage: http://codescratch.github.com/gizmo/
140
+ licenses:
141
+ - MIT
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ! '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ segments:
153
+ - 0
154
+ hash: -2973191327364084079
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ none: false
157
+ requirements:
158
+ - - ! '>='
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ segments:
162
+ - 0
163
+ hash: -2973191327364084079
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 1.8.24
167
+ signing_key:
168
+ specification_version: 3
169
+ summary: Removing the crud from developing an API to support CRUD, in the name of
170
+ Gizmo.
171
+ test_files: []