moon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +11 -0
- data/Rakefile +48 -0
- data/lib/moon.rb +15 -0
- data/lib/moon/action.rb +12 -0
- data/lib/moon/action/base.rb +16 -0
- data/lib/moon/action/model.rb +10 -0
- data/lib/moon/action/model/create.rb +45 -0
- data/lib/moon/action/model/destroy.rb +42 -0
- data/lib/moon/action/model/index.rb +31 -0
- data/lib/moon/action/model/show.rb +32 -0
- data/lib/moon/action/model/update.rb +42 -0
- data/lib/moon/action/models.rb +8 -0
- data/lib/moon/action/models/finder.rb +43 -0
- data/lib/moon/action/models/initializer.rb +51 -0
- data/lib/moon/action/models/updater.rb +36 -0
- data/lib/moon/action/rebuild_arrays.rb +40 -0
- data/lib/moon/action/reference_object.rb +35 -0
- data/lib/moon/action/valid_models_required.rb +21 -0
- data/lib/moon/application.rb +33 -0
- data/lib/moon/application/rack.rb +104 -0
- data/lib/moon/application/routes.rb +16 -0
- data/lib/moon/context.rb +25 -0
- data/lib/moon/formatter.rb +22 -0
- data/lib/moon/formatter/base.rb +15 -0
- data/lib/moon/formatter/generic.rb +29 -0
- data/lib/moon/response.rb +7 -0
- data/lib/moon/response/base.rb +18 -0
- data/lib/moon/response/json.rb +26 -0
- data/lib/moon/response/json/collection.rb +12 -0
- data/lib/moon/response/json/message.rb +18 -0
- data/lib/moon/response/json/model.rb +16 -0
- data/lib/moon/response/json/validation_errors.rb +9 -0
- data/lib/moon/utility.rb +7 -0
- data/lib/moon/utility/string.rb +17 -0
- data/lib/moon/utility/template.rb +23 -0
- data/lib/moon/validator.rb +71 -0
- data/lib/moon/validator/format.rb +32 -0
- data/lib/moon/validator/length.rb +42 -0
- data/lib/moon/validator/presence.rb +29 -0
- data/spec/lib/moon/action/base_spec.rb +29 -0
- data/spec/lib/moon/action/model/create_spec.rb +111 -0
- data/spec/lib/moon/action/model/destroy_spec.rb +84 -0
- data/spec/lib/moon/action/model/index_spec.rb +51 -0
- data/spec/lib/moon/action/model/show_spec.rb +51 -0
- data/spec/lib/moon/action/model/update_spec.rb +91 -0
- data/spec/lib/moon/action/models/finder_spec.rb +36 -0
- data/spec/lib/moon/action/models/initializer_spec.rb +43 -0
- data/spec/lib/moon/action/models/updater_spec.rb +36 -0
- data/spec/lib/moon/action/rebuild_arrays_spec.rb +28 -0
- data/spec/lib/moon/action/reference_object_spec.rb +32 -0
- data/spec/lib/moon/action/valid_models_required_spec.rb +64 -0
- data/spec/lib/moon/application/rack_spec.rb +73 -0
- data/spec/lib/moon/application/routes_spec.rb +26 -0
- data/spec/lib/moon/context_spec.rb +28 -0
- data/spec/lib/moon/formatter/base_spec.rb +30 -0
- data/spec/lib/moon/formatter/generic_spec.rb +34 -0
- data/spec/lib/moon/formatter_spec.rb +36 -0
- data/spec/lib/moon/response/base_spec.rb +33 -0
- data/spec/lib/moon/response/json/collection_spec.rb +30 -0
- data/spec/lib/moon/response/json/message_spec.rb +30 -0
- data/spec/lib/moon/response/json/model_spec.rb +31 -0
- data/spec/lib/moon/response/json/validation_errors_spec.rb +21 -0
- data/spec/lib/moon/response/json_spec.rb +33 -0
- data/spec/lib/moon/utility/string_spec.rb +29 -0
- data/spec/lib/moon/utility/template_spec.rb +49 -0
- data/spec/lib/moon/validator/format_spec.rb +40 -0
- data/spec/lib/moon/validator/length_spec.rb +60 -0
- data/spec/lib/moon/validator/presence_spec.rb +45 -0
- data/spec/lib/moon/validator_spec.rb +83 -0
- data/spec/spec_helper.rb +4 -0
- metadata +209 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
# Uses the parameters in the context and tries to update the models.
|
3
|
+
class Moon::Action::Models::Updater
|
4
|
+
|
5
|
+
def initialize(context)
|
6
|
+
@context = context
|
7
|
+
end
|
8
|
+
|
9
|
+
def perform
|
10
|
+
@context.models.each do |model_key, model|
|
11
|
+
Model.new(model, @context.parameters[model_key]).update
|
12
|
+
end
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
# Model updater.
|
17
|
+
class Model
|
18
|
+
|
19
|
+
def initialize(model, parameters = { })
|
20
|
+
@model, @parameters = model, parameters
|
21
|
+
end
|
22
|
+
|
23
|
+
def update
|
24
|
+
@parameters.each do |key, value|
|
25
|
+
method_name = "#{key}=".to_sym
|
26
|
+
@model.send method_name, value if @model.respond_to?(method_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.perform(context)
|
33
|
+
new(context).perform
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
# Converts each hash with numeric keys { "0" => "one", "1" => "two" } into arrays [ "one", "two" ].
|
3
|
+
class Moon::Action::RebuildArrays
|
4
|
+
|
5
|
+
def initialize(context)
|
6
|
+
@context = context
|
7
|
+
end
|
8
|
+
|
9
|
+
def perform
|
10
|
+
@context.parameters = self.class.rebuild_arrays @context.parameters
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def self.rebuild_arrays(parameters)
|
17
|
+
parameters.each do |key, value|
|
18
|
+
parameters[key] = rebuild_arrays value if value.is_a?(Hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
if array_hash?(parameters)
|
22
|
+
array = [ ]
|
23
|
+
parameters.keys.map(&:to_i).sort.each do |index|
|
24
|
+
array << parameters[index.to_s]
|
25
|
+
end
|
26
|
+
array
|
27
|
+
else
|
28
|
+
parameters
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.array_hash?(hash)
|
33
|
+
hash.size == hash.keys.select{ |key| key.to_s =~ /^\d+$/ }.size
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.perform(context)
|
37
|
+
new(context).perform
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
# The action creates a reference to the given store object and stores it in the given model attribute.
|
3
|
+
class Moon::Action::ReferenceObject
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
from, to = options.values_at :from, :to
|
7
|
+
@store_method, @store_key = from
|
8
|
+
@model_key, @model_attribute = to
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform(context)
|
12
|
+
@context = context
|
13
|
+
fetch_object
|
14
|
+
create_reference
|
15
|
+
store_reference
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def fetch_object
|
22
|
+
store = @context.send @store_method.to_sym
|
23
|
+
@object = store[@store_key.to_sym]
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_reference
|
27
|
+
@reference = GOM::Object.reference @object
|
28
|
+
end
|
29
|
+
|
30
|
+
def store_reference
|
31
|
+
model = @context.models[@model_key.to_sym]
|
32
|
+
model.send :"#{@model_attribute}=", @reference
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
# The action checks if every model in the context is valid.
|
3
|
+
class Moon::Action::ValidModelsRequired
|
4
|
+
|
5
|
+
def self.perform(context)
|
6
|
+
validation_errors = validate_models context
|
7
|
+
validation_errors.empty? ? nil : Moon::Response::JSON::ValidationErrors.new(validation_errors)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def self.validate_models(context)
|
13
|
+
validation_errors = { }
|
14
|
+
context.models.each do |key, model|
|
15
|
+
validator = Moon::Validator[model.class].new model
|
16
|
+
validation_errors[key] = validator.messages unless validator.ok?
|
17
|
+
end
|
18
|
+
validation_errors
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
# Main application class.
|
3
|
+
class Moon::Application
|
4
|
+
|
5
|
+
autoload :Rack, File.join(File.dirname(__FILE__), "application", "rack")
|
6
|
+
autoload :Routes, File.join(File.dirname(__FILE__), "application", "routes")
|
7
|
+
|
8
|
+
# This error is raised if an action doesn't respond to :perform.
|
9
|
+
class InvalidActionError < StandardError; end
|
10
|
+
|
11
|
+
# This error is raised if a response doesn't respond to :status, :headers and :body.
|
12
|
+
class InvalidResponseError < StandardError; end
|
13
|
+
|
14
|
+
attr_reader :routes
|
15
|
+
attr_reader :rack
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@routes = Routes.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def rack
|
22
|
+
@routes.rack.transponder
|
23
|
+
end
|
24
|
+
|
25
|
+
def environment
|
26
|
+
@rack.environment
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.storage_name(environment)
|
30
|
+
environment == :test ? :dump : :main
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
# Adapter for Rack-up servers. Sinatra is used as helper.
|
4
|
+
class Moon::Application::Rack
|
5
|
+
|
6
|
+
attr_reader :routes
|
7
|
+
attr_reader :transponder
|
8
|
+
attr_reader :environment
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
initialize_transponder
|
12
|
+
end
|
13
|
+
|
14
|
+
def routes=(routes)
|
15
|
+
@routes = routes
|
16
|
+
map_routes
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(*arguments)
|
20
|
+
@transponder.call *arguments
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def initialize_transponder
|
26
|
+
@transponder = Class.new Sinatra::Base
|
27
|
+
@transponder.enable :sessions
|
28
|
+
@transponder.disable :show_exceptions
|
29
|
+
@transponder.set :public, "public"
|
30
|
+
@transponder.error do
|
31
|
+
error = request.env["sinatra.error"]
|
32
|
+
error.message + "\n" + error.backtrace.join("\n")
|
33
|
+
end
|
34
|
+
@environment = @transponder.environment
|
35
|
+
end
|
36
|
+
|
37
|
+
def map_routes
|
38
|
+
@routes.each do |route|
|
39
|
+
Route.new(@transponder, route).define
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Abstraction for a application route
|
44
|
+
class Route
|
45
|
+
|
46
|
+
attr_reader :response
|
47
|
+
attr_reader :rack_response
|
48
|
+
|
49
|
+
def initialize(transponder, route)
|
50
|
+
@transponder = transponder
|
51
|
+
@environment = @transponder.environment
|
52
|
+
@route = route
|
53
|
+
end
|
54
|
+
|
55
|
+
def http_method
|
56
|
+
@route[:http_method]
|
57
|
+
end
|
58
|
+
|
59
|
+
def path
|
60
|
+
@route[:path]
|
61
|
+
end
|
62
|
+
|
63
|
+
def actions
|
64
|
+
@route[:actions] || [ ]
|
65
|
+
end
|
66
|
+
|
67
|
+
def define
|
68
|
+
route = self
|
69
|
+
@transponder.send http_method, path do
|
70
|
+
route.handle session, params
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle(session, params)
|
75
|
+
build_context session, params
|
76
|
+
perform_actions
|
77
|
+
build_rack_response
|
78
|
+
@rack_response
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def build_context(session, params)
|
84
|
+
@context = Moon::Context.new session, params
|
85
|
+
@context.storage_name = Moon::Application.storage_name @environment
|
86
|
+
end
|
87
|
+
|
88
|
+
def perform_actions
|
89
|
+
actions.each do |action|
|
90
|
+
raise Moon::Application::InvalidActionError, "Actions must implement a :perform method!" unless action.respond_to?(:perform)
|
91
|
+
@response = action.perform @context
|
92
|
+
return if @response
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_rack_response
|
97
|
+
raise Moon::Application::InvalidResponseError, "Responses must implement the :status, :headers and :body methods" unless
|
98
|
+
@response.respond_to?(:status) && @response.respond_to?(:headers) && @response.respond_to?(:body)
|
99
|
+
@rack_response = [ @response.status, @response.headers, @response.body ]
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'configure'
|
2
|
+
|
3
|
+
# Handler for the application routes.
|
4
|
+
class Moon::Application::Routes
|
5
|
+
|
6
|
+
attr_reader :rack
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@rack = Moon::Application::Rack.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def define(&block)
|
13
|
+
@rack.routes = [ Configure.process(&block)[:route] ].flatten
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/moon/context.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
# Context class that contains anything necessary to run a guard, action or response builder.
|
3
|
+
class Moon::Context
|
4
|
+
|
5
|
+
attr_accessor :storage_name
|
6
|
+
|
7
|
+
def initialize(session = { }, parameters = { })
|
8
|
+
@session, @parameters = session, parameters
|
9
|
+
end
|
10
|
+
|
11
|
+
[ :session, :parameters, :models, :collections ].each do |key|
|
12
|
+
|
13
|
+
define_method :"#{key}" do
|
14
|
+
instance_variable_set :"@#{key}", { } unless instance_variable_defined?(:"@#{key}")
|
15
|
+
instance_variable_get :"@#{key}"
|
16
|
+
end
|
17
|
+
|
18
|
+
define_method :"#{key}=" do |value|
|
19
|
+
raise ArgumentError, "#{key} must be a hash" unless value.is_a?(Hash)
|
20
|
+
instance_variable_set :"@#{key}", value
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
# Model formatter
|
3
|
+
class Moon::Formatter
|
4
|
+
|
5
|
+
autoload :Base, File.join(File.dirname(__FILE__), "formatter", "base")
|
6
|
+
autoload :Generic, File.join(File.dirname(__FILE__), "formatter", "generic")
|
7
|
+
|
8
|
+
def self.configuration=(value)
|
9
|
+
@configuration = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.configuration
|
13
|
+
@configuration
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.hash_for(model)
|
17
|
+
formatter_class = (@configuration || { })[model.class]
|
18
|
+
formatter_class ||= Moon::Formatter::Generic
|
19
|
+
formatter_class.new(model).hash
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
# Outputs a hash with all instance variables of the given model.
|
3
|
+
class Moon::Formatter::Generic < Moon::Formatter::Base
|
4
|
+
|
5
|
+
def hash
|
6
|
+
initialize_hash
|
7
|
+
inject_instance_variables
|
8
|
+
inject_id
|
9
|
+
@hash
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def initialize_hash
|
15
|
+
@hash = { }
|
16
|
+
end
|
17
|
+
|
18
|
+
def inject_instance_variables
|
19
|
+
@model.instance_variables.each do |variable_name|
|
20
|
+
key = variable_name.to_s.sub(/^@/, "").to_sym
|
21
|
+
@hash[key] = @model.instance_variable_get variable_name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def inject_id
|
26
|
+
@hash[:id] = GOM::Object.id @model
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
# Moon::Response::Base is the base class for all responses. It simply provides default values
|
3
|
+
# for status, headers and body
|
4
|
+
class Moon::Response::Base
|
5
|
+
|
6
|
+
def status
|
7
|
+
200
|
8
|
+
end
|
9
|
+
|
10
|
+
def headers
|
11
|
+
{ }
|
12
|
+
end
|
13
|
+
|
14
|
+
def body
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
# Moon::Response::JSON is the base class for all json responses.
|
4
|
+
class Moon::Response::JSON < Moon::Response::Base
|
5
|
+
|
6
|
+
autoload :Collection, File.join(File.dirname(__FILE__), "json", "collection")
|
7
|
+
autoload :Message, File.join(File.dirname(__FILE__), "json", "message")
|
8
|
+
autoload :Model, File.join(File.dirname(__FILE__), "json", "model")
|
9
|
+
autoload :ValidationErrors, File.join(File.dirname(__FILE__), "json", "validation_errors")
|
10
|
+
|
11
|
+
attr_accessor :status
|
12
|
+
attr_accessor :hash
|
13
|
+
|
14
|
+
def initialize(status, hash = { })
|
15
|
+
@status, @hash = status, hash
|
16
|
+
end
|
17
|
+
|
18
|
+
def headers
|
19
|
+
{ "Content-Type" => "application/json" }
|
20
|
+
end
|
21
|
+
|
22
|
+
def body
|
23
|
+
::JSON.generate hash
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|