happy 0.0.1 → 0.1.0.pre.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/happy.gemspec +5 -0
- data/lib/happy/actions.rb +78 -0
- data/lib/happy/context.rb +47 -0
- data/lib/happy/controller.rb +55 -0
- data/lib/happy/helpers.rb +37 -0
- data/lib/happy/permissions.rb +34 -0
- data/lib/happy/rackable.rb +24 -0
- data/lib/happy/request.rb +13 -0
- data/lib/happy/resources.rb +112 -0
- data/lib/happy/routing.rb +56 -0
- data/lib/happy/static.rb +7 -0
- data/lib/happy/version.rb +1 -1
- data/lib/happy.rb +11 -2
- metadata +59 -5
data/happy.gemspec
CHANGED
@@ -14,4 +14,9 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.name = "happy"
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Happy::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'activesupport', '~> 3.1'
|
19
|
+
gem.add_dependency 'rack', '~> 1.4'
|
20
|
+
gem.add_dependency 'happy-helpers' # TODO: , '~> 0.1.0'
|
21
|
+
gem.add_dependency 'allowance', '>= 0.1.1'
|
17
22
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Happy
|
2
|
+
module Actions
|
3
|
+
def serve!(data, options = {})
|
4
|
+
# Don't serve if there are still bits of path remaining.
|
5
|
+
return unless remaining_path.empty?
|
6
|
+
|
7
|
+
# Don't serve is data is not a string.
|
8
|
+
return unless data.is_a?(String)
|
9
|
+
|
10
|
+
# Mix in default options
|
11
|
+
options = {
|
12
|
+
layout: context.layout
|
13
|
+
}.merge(options)
|
14
|
+
|
15
|
+
# Add optional headers et al
|
16
|
+
response.status = options[:status] if options.has_key?(:status)
|
17
|
+
response['Content-type'] = options[:content_type] if options.has_key?(:content_type)
|
18
|
+
|
19
|
+
# Apply layout, if available
|
20
|
+
if options[:layout]
|
21
|
+
data = render(options[:layout]) { data }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Set response body and finish request
|
25
|
+
response.body = [data]
|
26
|
+
halt!
|
27
|
+
end
|
28
|
+
|
29
|
+
def halt!(message = :done)
|
30
|
+
throw message
|
31
|
+
end
|
32
|
+
|
33
|
+
def redirect!(to, status = 302)
|
34
|
+
header "Location", url_for(to)
|
35
|
+
response.status = status
|
36
|
+
halt!
|
37
|
+
end
|
38
|
+
|
39
|
+
def layout(name)
|
40
|
+
context.layout = name
|
41
|
+
end
|
42
|
+
|
43
|
+
def content_type(type)
|
44
|
+
header 'Content-type', type
|
45
|
+
end
|
46
|
+
|
47
|
+
def max_age(t, options = {})
|
48
|
+
options = {
|
49
|
+
:public => true,
|
50
|
+
:must_revalidate => true
|
51
|
+
}.merge(options)
|
52
|
+
|
53
|
+
s = []
|
54
|
+
s << 'public' if options[:public]
|
55
|
+
s << 'must-revalidate' if options[:must_revalidate]
|
56
|
+
s << "max-age=#{t.to_i}"
|
57
|
+
|
58
|
+
cache_control s.join(', ')
|
59
|
+
end
|
60
|
+
|
61
|
+
def cache_control(s)
|
62
|
+
header 'Cache-control', s
|
63
|
+
end
|
64
|
+
|
65
|
+
def header(name, value)
|
66
|
+
response[name] = value
|
67
|
+
end
|
68
|
+
|
69
|
+
def invoke(klass, options = {}, &blk)
|
70
|
+
klass.new(env, options, &blk).perform
|
71
|
+
end
|
72
|
+
|
73
|
+
def run(app)
|
74
|
+
context.response = app.call(request.env)
|
75
|
+
halt!
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'happy/request'
|
2
|
+
require 'happy/helpers'
|
3
|
+
|
4
|
+
module Happy
|
5
|
+
class Context
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
attr_reader :request, :response, :remaining_path
|
9
|
+
attr_accessor :layout, :controller
|
10
|
+
delegate :params, :session, :to => :request
|
11
|
+
|
12
|
+
def initialize(request, response)
|
13
|
+
@request = request
|
14
|
+
@response = response
|
15
|
+
@remaining_path = @request.path.split('/').reject {|s| s.blank? }
|
16
|
+
@layout = nil
|
17
|
+
@controller = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def with_controller(new_controller)
|
21
|
+
# remember previous controller
|
22
|
+
old_controller = self.controller
|
23
|
+
self.controller = new_controller
|
24
|
+
|
25
|
+
# execute permissions block
|
26
|
+
controller.class.permissions_blk.try(:call, permissions, self)
|
27
|
+
|
28
|
+
# execute block
|
29
|
+
yield
|
30
|
+
ensure
|
31
|
+
# switch back to previous controller
|
32
|
+
self.controller = old_controller
|
33
|
+
end
|
34
|
+
|
35
|
+
def response=(r)
|
36
|
+
@response = r
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
class << self
|
42
|
+
def from_env(env)
|
43
|
+
new(Happy::Request.new(env), Rack::Response.new)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'happy/routing'
|
2
|
+
require 'happy/actions'
|
3
|
+
require 'happy/rackable'
|
4
|
+
|
5
|
+
module Happy
|
6
|
+
class Controller
|
7
|
+
include Routing
|
8
|
+
include Actions
|
9
|
+
include Rackable
|
10
|
+
|
11
|
+
attr_reader :options, :env
|
12
|
+
|
13
|
+
delegate :request, :response, :remaining_path, :params, :session,
|
14
|
+
:render, :url_for,
|
15
|
+
:to => :context
|
16
|
+
|
17
|
+
def initialize(env = nil, options = {}, &blk)
|
18
|
+
@env = env
|
19
|
+
@options = options
|
20
|
+
instance_exec(&blk) if blk
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform
|
24
|
+
context.with_controller(self) do
|
25
|
+
route
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def context
|
32
|
+
@env['happy.context'] ||= self.class.context_class.from_env(@env)
|
33
|
+
end
|
34
|
+
|
35
|
+
def route
|
36
|
+
instance_exec(&self.class.route_blk) if self.class.route_blk
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
attr_reader :route_blk
|
41
|
+
|
42
|
+
def route(&blk)
|
43
|
+
@route_blk = blk
|
44
|
+
end
|
45
|
+
|
46
|
+
def context(&blk)
|
47
|
+
context_class.class_exec(&blk)
|
48
|
+
end
|
49
|
+
|
50
|
+
def context_class
|
51
|
+
Happy::Context
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'happy-helpers'
|
2
|
+
|
3
|
+
module Happy
|
4
|
+
module Helpers
|
5
|
+
include HappyHelpers::Helpers
|
6
|
+
|
7
|
+
def render(what, options = {}, &blk)
|
8
|
+
case what
|
9
|
+
when String then render_template(what, options, &blk)
|
10
|
+
when Enumerable then what.map { |i| render(i, options, &blk) }.join
|
11
|
+
else render_resource(what, options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def render_template(name, variables = {}, &blk)
|
16
|
+
HappyHelpers::Templates.render(name, self, variables, &blk)
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_resource(resource, options = {})
|
20
|
+
# build name strings
|
21
|
+
singular_name = resource.class.to_s.tableize.singularize
|
22
|
+
plural_name = singular_name.pluralize
|
23
|
+
|
24
|
+
# set options
|
25
|
+
options = {
|
26
|
+
singular_name => resource
|
27
|
+
}.merge(options)
|
28
|
+
|
29
|
+
# render
|
30
|
+
render_template("#{plural_name}/_#{singular_name}.html.haml", options)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :h, :escape_html
|
34
|
+
alias_method :l, :localize
|
35
|
+
alias_method :t, :translate
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Happy
|
2
|
+
module Permissions
|
3
|
+
module ContextExtensions
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def permissions(&blk)
|
7
|
+
@permissions ||= Allowance.define
|
8
|
+
end
|
9
|
+
|
10
|
+
def can?(*args)
|
11
|
+
permissions.allowed?(*args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ControllerExtensions
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
|
18
|
+
included do
|
19
|
+
delegate :can?, :to => :context
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
attr_accessor :permissions_blk
|
24
|
+
|
25
|
+
def permissions(&blk)
|
26
|
+
self.permissions_blk = blk
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Happy::Context.send(:include, Happy::Permissions::ContextExtensions)
|
34
|
+
Happy::Controller.send(:include, Happy::Permissions::ControllerExtensions)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Happy
|
2
|
+
module Rackable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def call(env)
|
6
|
+
@env = env
|
7
|
+
|
8
|
+
catch :done do
|
9
|
+
serve! perform
|
10
|
+
|
11
|
+
# If we get here, #serve decided not to serve.
|
12
|
+
raise NotFoundError
|
13
|
+
end
|
14
|
+
|
15
|
+
response
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def call(env)
|
20
|
+
new.call(env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Happy
|
2
|
+
module Resources
|
3
|
+
module ControllerExtensions
|
4
|
+
def resource(klass, options = {}, &blk)
|
5
|
+
invoke Happy::Resources::ResourceMounter, options.merge(:class => klass), &blk
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class ResourceMounter < Happy::Controller
|
10
|
+
def render_resource_template(name)
|
11
|
+
render "#{options[:plural_name]}/#{name}.html.haml"
|
12
|
+
end
|
13
|
+
|
14
|
+
def resource
|
15
|
+
options[:class]
|
16
|
+
end
|
17
|
+
|
18
|
+
def resource_with_permission_scope(*args)
|
19
|
+
context.permissions.scoped_model(*args, options[:class])
|
20
|
+
end
|
21
|
+
|
22
|
+
def require_permission!(*args)
|
23
|
+
raise "not allowed" unless can?(*args, options[:class])
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_plural_variable(v)
|
27
|
+
context.instance_variable_set "@#{options[:plural_name]}", v
|
28
|
+
end
|
29
|
+
|
30
|
+
def plural_variable
|
31
|
+
context.instance_variable_get "@#{options[:plural_name]}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_singular_variable(v)
|
35
|
+
context.instance_variable_set "@#{options[:singular_name]}", v
|
36
|
+
end
|
37
|
+
|
38
|
+
def singular_variable
|
39
|
+
context.instance_variable_get "@#{options[:singular_name]}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def do_index
|
43
|
+
require_permission! :index
|
44
|
+
set_plural_variable resource_with_permission_scope(:index).all
|
45
|
+
render_resource_template 'index'
|
46
|
+
end
|
47
|
+
|
48
|
+
def do_show
|
49
|
+
require_permission! :show
|
50
|
+
set_singular_variable resource_with_permission_scope(:show).find(params['id'])
|
51
|
+
render_resource_template 'show'
|
52
|
+
end
|
53
|
+
|
54
|
+
def do_new
|
55
|
+
require_permission! :new
|
56
|
+
set_singular_variable resource_with_permission_scope(:new).new(params[options[:singular_name]], :as => options[:role])
|
57
|
+
render_resource_template 'new'
|
58
|
+
end
|
59
|
+
|
60
|
+
def do_create
|
61
|
+
require_permission! :create
|
62
|
+
set_singular_variable resource_with_permission_scope(:create).new(params[options[:singular_name]], :as => options[:role])
|
63
|
+
|
64
|
+
if singular_variable.save
|
65
|
+
redirect! singular_variable
|
66
|
+
else
|
67
|
+
render_resource_template 'new'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def do_edit
|
72
|
+
require_permission! :edit
|
73
|
+
set_singular_variable resource_with_permission_scope(:edit).find(params['id'])
|
74
|
+
render_resource_template 'edit'
|
75
|
+
end
|
76
|
+
|
77
|
+
def do_update
|
78
|
+
require_permission! :update
|
79
|
+
set_singular_variable resource_with_permission_scope(:update).find(params['id'])
|
80
|
+
singular_variable.assign_attributes params[options[:singular_name]], :as => options[:role]
|
81
|
+
|
82
|
+
if singular_variable.save
|
83
|
+
redirect! singular_variable
|
84
|
+
else
|
85
|
+
render_resource_template 'edit'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def route
|
90
|
+
@options = {
|
91
|
+
singular_name: options[:class].to_s.tableize.singularize,
|
92
|
+
plural_name: options[:class].to_s.tableize.pluralize
|
93
|
+
}.merge(@options)
|
94
|
+
|
95
|
+
path options[:plural_name] do
|
96
|
+
get('new') { do_new }
|
97
|
+
|
98
|
+
path :id do
|
99
|
+
get { do_show }
|
100
|
+
post { do_update }
|
101
|
+
get('edit') { do_edit }
|
102
|
+
end
|
103
|
+
|
104
|
+
post { do_create }
|
105
|
+
get { do_index }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
Happy::Controller.send(:include, Happy::Resources::ControllerExtensions)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Happy
|
2
|
+
module Routing
|
3
|
+
def path_to_regexp(path)
|
4
|
+
path = ":#{path}" if path.is_a?(Symbol)
|
5
|
+
Regexp.compile('^'+path.gsub(/\)/, ')?').gsub(/\//, '\/').gsub(/\./, '\.').gsub(/:(\w+)/, '(?<\\1>.+)')+'$')
|
6
|
+
end
|
7
|
+
|
8
|
+
def path(*args, &blk)
|
9
|
+
options = (args.pop if args.last.is_a?(Hash)) || {}
|
10
|
+
args = [nil] if args.empty?
|
11
|
+
|
12
|
+
args.each do |name|
|
13
|
+
if name.present?
|
14
|
+
path_match = path_to_regexp(name).match(remaining_path.first)
|
15
|
+
end
|
16
|
+
|
17
|
+
method_matched = [nil, request.request_method.downcase.to_sym].include?(options[:method])
|
18
|
+
path_matched = (path_match || (name.nil? && remaining_path.empty?))
|
19
|
+
|
20
|
+
# Only do something here if method and requested path both match
|
21
|
+
if path_matched && method_matched
|
22
|
+
# Transfer variables contained in path name to params hash
|
23
|
+
if path_match
|
24
|
+
path_match.names.each { |k| request.params[k] = path_match[k] }
|
25
|
+
remaining_path.shift
|
26
|
+
end
|
27
|
+
|
28
|
+
serve! instance_exec(&blk)
|
29
|
+
|
30
|
+
# If we get here, #serve decided not to serve.
|
31
|
+
raise NotFoundError
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def get(*args, &blk)
|
37
|
+
args.last.is_a?(Hash) ? args.last.merge(method: :get) : args.push(method: :get)
|
38
|
+
path(*args, &blk)
|
39
|
+
end
|
40
|
+
|
41
|
+
def post(*args, &blk)
|
42
|
+
args.last.is_a?(Hash) ? args.last.merge(method: :post) : args.push(method: :post)
|
43
|
+
path(*args, &blk)
|
44
|
+
end
|
45
|
+
|
46
|
+
def put(*args, &blk)
|
47
|
+
args.last.is_a?(Hash) ? args.last.merge(method: :put) : args.push(method: :put)
|
48
|
+
path(*args, &blk)
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete(*args, &blk)
|
52
|
+
args.last.is_a?(Hash) ? args.last.merge(method: :delete) : args.push(method: :delete)
|
53
|
+
path(*args, &blk)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/happy/static.rb
ADDED
data/lib/happy/version.rb
CHANGED
data/lib/happy.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
|
-
require
|
1
|
+
require 'active_support/all' # SMELL
|
2
|
+
|
3
|
+
require 'happy/context'
|
4
|
+
require 'happy/controller'
|
5
|
+
require 'happy/static'
|
2
6
|
|
3
7
|
module Happy
|
4
|
-
|
8
|
+
class HappyError < StandardError ; end
|
9
|
+
class NotFoundError < HappyError ; end
|
10
|
+
|
11
|
+
def self.env
|
12
|
+
ActiveSupport::StringInquirer.new(ENV['RACK_ENV'] || 'development')
|
13
|
+
end
|
5
14
|
end
|
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: happy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0.pre.1
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Hendrik Mans
|
@@ -10,7 +10,51 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
date: 2012-05-31 00:00:00.000000000 Z
|
13
|
-
dependencies:
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: &70164769147200 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.1'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70164769147200
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rack
|
27
|
+
requirement: &70164769146300 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.4'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70164769146300
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: happy-helpers
|
38
|
+
requirement: &70164769145860 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70164769145860
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: allowance
|
49
|
+
requirement: &70164769145220 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.1.1
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70164769145220
|
14
58
|
description: A happy little toolkit for writing web applications.
|
15
59
|
email:
|
16
60
|
- hendrik@mans.de
|
@@ -25,6 +69,16 @@ files:
|
|
25
69
|
- Rakefile
|
26
70
|
- happy.gemspec
|
27
71
|
- lib/happy.rb
|
72
|
+
- lib/happy/actions.rb
|
73
|
+
- lib/happy/context.rb
|
74
|
+
- lib/happy/controller.rb
|
75
|
+
- lib/happy/helpers.rb
|
76
|
+
- lib/happy/permissions.rb
|
77
|
+
- lib/happy/rackable.rb
|
78
|
+
- lib/happy/request.rb
|
79
|
+
- lib/happy/resources.rb
|
80
|
+
- lib/happy/routing.rb
|
81
|
+
- lib/happy/static.rb
|
28
82
|
- lib/happy/version.rb
|
29
83
|
homepage: https://github.com/hmans/happy
|
30
84
|
licenses: []
|
@@ -41,9 +95,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
95
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
96
|
none: false
|
43
97
|
requirements:
|
44
|
-
- - ! '
|
98
|
+
- - ! '>'
|
45
99
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
100
|
+
version: 1.3.1
|
47
101
|
requirements: []
|
48
102
|
rubyforge_project:
|
49
103
|
rubygems_version: 1.8.11
|