moon 0.0.1
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 +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
|