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.
Files changed (72) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +11 -0
  3. data/Rakefile +48 -0
  4. data/lib/moon.rb +15 -0
  5. data/lib/moon/action.rb +12 -0
  6. data/lib/moon/action/base.rb +16 -0
  7. data/lib/moon/action/model.rb +10 -0
  8. data/lib/moon/action/model/create.rb +45 -0
  9. data/lib/moon/action/model/destroy.rb +42 -0
  10. data/lib/moon/action/model/index.rb +31 -0
  11. data/lib/moon/action/model/show.rb +32 -0
  12. data/lib/moon/action/model/update.rb +42 -0
  13. data/lib/moon/action/models.rb +8 -0
  14. data/lib/moon/action/models/finder.rb +43 -0
  15. data/lib/moon/action/models/initializer.rb +51 -0
  16. data/lib/moon/action/models/updater.rb +36 -0
  17. data/lib/moon/action/rebuild_arrays.rb +40 -0
  18. data/lib/moon/action/reference_object.rb +35 -0
  19. data/lib/moon/action/valid_models_required.rb +21 -0
  20. data/lib/moon/application.rb +33 -0
  21. data/lib/moon/application/rack.rb +104 -0
  22. data/lib/moon/application/routes.rb +16 -0
  23. data/lib/moon/context.rb +25 -0
  24. data/lib/moon/formatter.rb +22 -0
  25. data/lib/moon/formatter/base.rb +15 -0
  26. data/lib/moon/formatter/generic.rb +29 -0
  27. data/lib/moon/response.rb +7 -0
  28. data/lib/moon/response/base.rb +18 -0
  29. data/lib/moon/response/json.rb +26 -0
  30. data/lib/moon/response/json/collection.rb +12 -0
  31. data/lib/moon/response/json/message.rb +18 -0
  32. data/lib/moon/response/json/model.rb +16 -0
  33. data/lib/moon/response/json/validation_errors.rb +9 -0
  34. data/lib/moon/utility.rb +7 -0
  35. data/lib/moon/utility/string.rb +17 -0
  36. data/lib/moon/utility/template.rb +23 -0
  37. data/lib/moon/validator.rb +71 -0
  38. data/lib/moon/validator/format.rb +32 -0
  39. data/lib/moon/validator/length.rb +42 -0
  40. data/lib/moon/validator/presence.rb +29 -0
  41. data/spec/lib/moon/action/base_spec.rb +29 -0
  42. data/spec/lib/moon/action/model/create_spec.rb +111 -0
  43. data/spec/lib/moon/action/model/destroy_spec.rb +84 -0
  44. data/spec/lib/moon/action/model/index_spec.rb +51 -0
  45. data/spec/lib/moon/action/model/show_spec.rb +51 -0
  46. data/spec/lib/moon/action/model/update_spec.rb +91 -0
  47. data/spec/lib/moon/action/models/finder_spec.rb +36 -0
  48. data/spec/lib/moon/action/models/initializer_spec.rb +43 -0
  49. data/spec/lib/moon/action/models/updater_spec.rb +36 -0
  50. data/spec/lib/moon/action/rebuild_arrays_spec.rb +28 -0
  51. data/spec/lib/moon/action/reference_object_spec.rb +32 -0
  52. data/spec/lib/moon/action/valid_models_required_spec.rb +64 -0
  53. data/spec/lib/moon/application/rack_spec.rb +73 -0
  54. data/spec/lib/moon/application/routes_spec.rb +26 -0
  55. data/spec/lib/moon/context_spec.rb +28 -0
  56. data/spec/lib/moon/formatter/base_spec.rb +30 -0
  57. data/spec/lib/moon/formatter/generic_spec.rb +34 -0
  58. data/spec/lib/moon/formatter_spec.rb +36 -0
  59. data/spec/lib/moon/response/base_spec.rb +33 -0
  60. data/spec/lib/moon/response/json/collection_spec.rb +30 -0
  61. data/spec/lib/moon/response/json/message_spec.rb +30 -0
  62. data/spec/lib/moon/response/json/model_spec.rb +31 -0
  63. data/spec/lib/moon/response/json/validation_errors_spec.rb +21 -0
  64. data/spec/lib/moon/response/json_spec.rb +33 -0
  65. data/spec/lib/moon/utility/string_spec.rb +29 -0
  66. data/spec/lib/moon/utility/template_spec.rb +49 -0
  67. data/spec/lib/moon/validator/format_spec.rb +40 -0
  68. data/spec/lib/moon/validator/length_spec.rb +60 -0
  69. data/spec/lib/moon/validator/presence_spec.rb +45 -0
  70. data/spec/lib/moon/validator_spec.rb +83 -0
  71. data/spec/spec_helper.rb +4 -0
  72. 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
@@ -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,15 @@
1
+
2
+ # Base class for a formatters.
3
+ class Moon::Formatter::Base
4
+
5
+ attr_accessor :model
6
+
7
+ def initialize(model)
8
+ @model = model
9
+ end
10
+
11
+ def hash
12
+ raise NotImplementedError, "method :hash has not been implemented"
13
+ end
14
+
15
+ 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,7 @@
1
+
2
+ module Moon::Response
3
+
4
+ autoload :Base, File.join(File.dirname(__FILE__), "response", "base")
5
+ autoload :JSON, File.join(File.dirname(__FILE__), "response", "json")
6
+
7
+ 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