happy 0.1.0.pre9 → 0.1.0.pre10
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +124 -17
- data/happy.gemspec +1 -1
- data/lib/happy/context.rb +2 -1
- data/lib/happy/controller/routing.rb +1 -1
- data/lib/happy/controller.rb +19 -2
- data/lib/happy/ext/resources.rb +8 -0
- data/lib/happy/version.rb +1 -1
- data/spec/controller_spec.rb +77 -0
- metadata +22 -22
data/README.md
CHANGED
@@ -1,29 +1,136 @@
|
|
1
|
-
# Happy
|
1
|
+
# Happy Ruby
|
2
2
|
|
3
|
-
|
3
|
+
**The Happy Web Application Toolkit for Ruby.**
|
4
4
|
|
5
|
-
##
|
5
|
+
## Introduction
|
6
6
|
|
7
|
-
|
7
|
+
Happy is a toolkit for developing web applications using Ruby. Inspired by both Sinatra and Rails, it sits somewhere in the middle, trying to offer the super-light-weight attitude and flexibility of Sinatra, the comfort and power of Rails, and adding a big chunk of extensibility and modularity that any lover of object-oriented application design will enjoy.
|
8
8
|
|
9
|
-
|
9
|
+
Furthermore, the way Happy handles incoming requests is vastly different from how most of the other frameworks do it, offering a new, extremely flexible and, yes, fun way of building your application.
|
10
10
|
|
11
|
-
|
11
|
+
### Examples
|
12
12
|
|
13
|
-
|
13
|
+
"Hello world" with Happy:
|
14
14
|
|
15
|
-
|
15
|
+
```ruby
|
16
|
+
# config.ru
|
17
|
+
require 'happy'
|
16
18
|
|
17
|
-
|
19
|
+
class MyApp < Happy::Controller
|
20
|
+
route do
|
21
|
+
'Hello world'
|
22
|
+
end
|
23
|
+
end
|
18
24
|
|
19
|
-
|
25
|
+
run MyApp
|
26
|
+
```
|
20
27
|
|
21
|
-
|
28
|
+
How about something a little bit closer to reality?
|
22
29
|
|
23
|
-
|
30
|
+
``` ruby
|
31
|
+
# config.ru
|
32
|
+
require 'happy'
|
24
33
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
34
|
+
class MyApp < Happy::Controller
|
35
|
+
route do
|
36
|
+
# Set a default layout for all responses!
|
37
|
+
layout 'application.erb'
|
38
|
+
|
39
|
+
# Happily deal with sub-paths!
|
40
|
+
path 'hello' do
|
41
|
+
# Let's use a different layout here.
|
42
|
+
layout 'hello_layout.erb'
|
43
|
+
|
44
|
+
# Happily deal with parameters contained in paths!
|
45
|
+
path :name do
|
46
|
+
# Just return a string to happily render it!
|
47
|
+
"Hello, #{params['name']}"!
|
48
|
+
end
|
49
|
+
|
50
|
+
"Silly user, didn't provide a name!"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Now let's do something a little bit more exciting!
|
54
|
+
#
|
55
|
+
# How about passing control to another controller? In this instance,
|
56
|
+
# we're invoking an instance of ResourceMounter, a controller class
|
57
|
+
# that serves a model resource RESTful-Rails-style. We'll save a
|
58
|
+
# reference to the controller for later.
|
59
|
+
|
60
|
+
articles = ResourceMounter.new(:class => Article)
|
61
|
+
articles.perform
|
62
|
+
|
63
|
+
# Or use the shortcut: invoke :resource_mounter, :class => Article
|
64
|
+
|
65
|
+
# This block of code is executed for every request, so you can do
|
66
|
+
# some crazy stuff here, including only defining specific paths
|
67
|
+
# when certain conditions are given. Just write Ruby! :)
|
68
|
+
|
69
|
+
if context.user_is_admin?
|
70
|
+
path 'delete_everything' do
|
71
|
+
Article.delete_all
|
72
|
+
|
73
|
+
# How about rendering a view template and passing
|
74
|
+
# variables to it?
|
75
|
+
render 'admin_message.erb',
|
76
|
+
:message => 'You just deleted everything. Grats!'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# If we reach this point, the request still hasn't been handled, so
|
81
|
+
# the user must by trying to access the root URL. How about a redirect
|
82
|
+
# to the URL of the previously invoked controller?
|
83
|
+
|
84
|
+
redirect! articles.root_url
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# It's just a Rack app, so let's run it.
|
89
|
+
run MyApp
|
90
|
+
```
|
91
|
+
|
92
|
+
|
93
|
+
### The Basic Building Blocks
|
94
|
+
|
95
|
+
Each and every web application built with Happy revolves around two core building blocks: the **context** and one or more **controllers**.
|
96
|
+
|
97
|
+
The **context** is a wrapper around the request currently being handled by your application. It holds information about the request itself, the response being rendered, and any piece of application logic that's scoped to individual requests (like authentication or authorization related code.) Happy doesn't have view helpers (like Rails); instead, view templates are rendered within the scope of the current context.
|
98
|
+
|
99
|
+
**Controllers** are building blocks that mostly deal with routing and acting on request URLs. Unlike controllers in Rails, instead of deciding on what controller to run depending on the requested URL, each request is immediately passed to a **root controller** which then decides how to continue. Controllers can serve content, parse the URL for paths, or even pass on control over the request to a different controller.
|
100
|
+
|
101
|
+
Controllers are the magic behind Happy; they're essentially **re-useable, self-contained, easily testable** web application classes that can be mounted at any URL you require. They can be as simple as serving a single response or as complex as a complete admin backend or blog engine. And finally, Happy Controllers are also **Rack applications**, so you can even use them anywhere you can mount Rack apps!
|
102
|
+
|
103
|
+
|
104
|
+
### More Happy Features
|
105
|
+
|
106
|
+
In addition to its fun and flexible approach to writing web applications, Happy boasts the following features, and more:
|
107
|
+
|
108
|
+
* Run-time permission management provided by [Allowance](https://github.com/hmans/allowance).
|
109
|
+
* A set of Happy Controllers making you super-productive, from serving static files to minifying JavaScript assets or even serving complete RESTful resources.
|
110
|
+
* A set of view helpers including [simple_form](https://github.com/plataformatec/simple_form) inspired forms on auto-pilot, with full support for translations and localization through [I18n](https://github.com/svenfuchs/i18n).
|
111
|
+
* Happy _loves_ Rack. You can mount Rack Middleware as well as pass control to another Rack application, just like that. Or use Happy Controllers from within other applications -- they're just Rack apps. And, of course, serve your Happy application through any Rack-compliant Rack server.
|
112
|
+
|
113
|
+
|
114
|
+
### Happy is Opinionated Software
|
115
|
+
|
116
|
+
Happy is opinionated software. Its design and functionality follow a certain set of opinions, a few of which are:
|
117
|
+
|
118
|
+
* **The core framework should be very simple**. As described above, the entire framework revolves around two important classes (Happy::Controller and Happy::Context). Everything that's not directly related to the core framework should be provided by additional gems. (For example, anything that is view helper related has been moved to the [HappyHelpers](https://github.com/hmans/happy-helpers) gem, and the permission management code has been extracted to the [Allowance](https://github.com/hmans/allowance) gem.) Happy isn't trying to be a kitchen sink framework.
|
119
|
+
* **Full-stack is boring**. Sure, it's nice if your framework of choice is holding your hands almost every step you go, but it's when you get to that point where you want to do something _differently_ from what your framework provides to you that you'll start hitting walls. For example, Happy doesn't generate data layer code for you; you're expected (and free to) simply use any ORM (or whatever) you desire, if any. For example, adding [Mongoid](http://mongoid.org/) to your Happy project is just two lines of code. Don't worry, we'll provide recipes; you don't need generators for that kind of stuff.
|
120
|
+
* **Routing sucks**. Pretty much all other Ruby web application frameworks go to great lengths to map incoming requests to code through regular expressions (or DSLs allowing you to specify routes that are then compiled to regular expressions behind the scenes.) It's a very fast, but somewhat inflexible approach.
|
121
|
+
* **Objects are awesome**. Frameworks like Rails are, technically speaking, object-oriented, but really only use classes mostly as containers for code. Requests are mapped (through the aforementioned routes) to controller classes, which are then instantiated and passed control to. It works, but it's also really boring. Objects are fun! Happy uses the request URL as a roadmap through your application's object graph. It allows you to think of your application as a nested tree of _things_.
|
122
|
+
|
123
|
+
If you find that you agree with these, you're going to _love_ building Happy web applications.
|
124
|
+
|
125
|
+
|
126
|
+
## Current Status
|
127
|
+
|
128
|
+
Happy is being extracted from a web application that I've been working on. I'm trying to get a first, hopefully somewhat stable release out of the door some time during June 2012.
|
129
|
+
|
130
|
+
FWIW, here's a list of important things still missing right now:
|
131
|
+
|
132
|
+
* Nicer error pages for 404s, 401s etc.
|
133
|
+
* Better logging.
|
134
|
+
* Improved view engine compatibility.
|
135
|
+
|
136
|
+
Hendrik Mans, hendrik@mans.de
|
data/happy.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
|
|
17
17
|
|
18
18
|
gem.add_dependency 'activesupport', '~> 3.1'
|
19
19
|
gem.add_dependency 'rack', '~> 1.4'
|
20
|
-
gem.add_dependency 'happy-helpers', '~> 0.1.0.
|
20
|
+
gem.add_dependency 'happy-helpers', '~> 0.1.0.pre8'
|
21
21
|
gem.add_dependency 'allowance', '>= 0.1.1'
|
22
22
|
|
23
23
|
gem.add_dependency 'happy-cli', '>= 0.1.0.pre1'
|
data/lib/happy/context.rb
CHANGED
@@ -5,7 +5,7 @@ module Happy
|
|
5
5
|
class Context
|
6
6
|
include Helpers
|
7
7
|
|
8
|
-
attr_reader :request, :response, :remaining_path
|
8
|
+
attr_reader :request, :response, :previous_path, :remaining_path
|
9
9
|
attr_accessor :layout, :controller
|
10
10
|
delegate :params, :session, :to => :request
|
11
11
|
|
@@ -13,6 +13,7 @@ module Happy
|
|
13
13
|
@request = request
|
14
14
|
@response = response
|
15
15
|
@remaining_path = @request.path.split('/').reject {|s| s.blank? }
|
16
|
+
@previous_path = []
|
16
17
|
@layout = nil
|
17
18
|
@controller = nil
|
18
19
|
end
|
data/lib/happy/controller.rb
CHANGED
@@ -8,15 +8,22 @@ module Happy
|
|
8
8
|
include Actions
|
9
9
|
include Rackable
|
10
10
|
|
11
|
-
attr_reader :options, :env
|
11
|
+
attr_reader :options, :env, :root_path
|
12
12
|
|
13
|
-
delegate :request, :response, :
|
13
|
+
delegate :request, :response, :params, :session,
|
14
|
+
:previous_path, :remaining_path,
|
14
15
|
:render, :url_for,
|
15
16
|
:to => :context
|
16
17
|
|
17
18
|
def initialize(env = {}, options = {}, &blk)
|
18
19
|
@env = env
|
19
20
|
@options = options
|
21
|
+
|
22
|
+
# Save a copy of the current path as this controller's root path.
|
23
|
+
@root_path = context.previous_path.dup
|
24
|
+
|
25
|
+
# Execute block against this instance, allowing the controller to
|
26
|
+
# provide a DSL for configuration.
|
20
27
|
instance_exec(&blk) if blk
|
21
28
|
end
|
22
29
|
|
@@ -26,6 +33,16 @@ module Happy
|
|
26
33
|
end
|
27
34
|
end
|
28
35
|
|
36
|
+
protected
|
37
|
+
|
38
|
+
def url(extras = nil)
|
39
|
+
url_for(previous_path, extras)
|
40
|
+
end
|
41
|
+
|
42
|
+
def root_url(extras = nil)
|
43
|
+
url_for(root_path, extras)
|
44
|
+
end
|
45
|
+
|
29
46
|
private
|
30
47
|
|
31
48
|
def context
|
data/lib/happy/ext/resources.rb
CHANGED
@@ -10,6 +10,10 @@ module Happy
|
|
10
10
|
end
|
11
11
|
|
12
12
|
class ResourceMounter < Happy::Controller
|
13
|
+
def root_url
|
14
|
+
super(options[:plural_name])
|
15
|
+
end
|
16
|
+
|
13
17
|
def render_resource_template(name)
|
14
18
|
render "#{options[:plural_name]}/#{name}.html.haml"
|
15
19
|
end
|
@@ -95,10 +99,14 @@ module Happy
|
|
95
99
|
:plural_name => options[:class].to_s.tableize.pluralize
|
96
100
|
}.merge(@options)
|
97
101
|
|
102
|
+
puts url
|
103
|
+
|
98
104
|
path options[:plural_name] do
|
105
|
+
puts url
|
99
106
|
get('new') { do_new }
|
100
107
|
|
101
108
|
path :id do
|
109
|
+
puts url
|
102
110
|
get { do_show }
|
103
111
|
post { do_update }
|
104
112
|
get('edit') { do_edit }
|
data/lib/happy/version.rb
CHANGED
data/spec/controller_spec.rb
CHANGED
@@ -32,5 +32,82 @@ module Happy
|
|
32
32
|
last_response.body.should == 'yay!'
|
33
33
|
end
|
34
34
|
end
|
35
|
+
|
36
|
+
describe '#url' do
|
37
|
+
it "returns the current URL" do
|
38
|
+
def app
|
39
|
+
build_controller do
|
40
|
+
path 'foo' do
|
41
|
+
path 'bar' do
|
42
|
+
"My URL is #{url}"
|
43
|
+
end
|
44
|
+
|
45
|
+
"My URL is #{url}"
|
46
|
+
end
|
47
|
+
|
48
|
+
"My URL is #{url}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
response_for { get '/' }.body.should == 'My URL is /'
|
53
|
+
response_for { get '/foo' }.body.should == 'My URL is /foo'
|
54
|
+
response_for { get '/foo/bar' }.body.should == 'My URL is /foo/bar'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "appends extra paths to the URL if provided" do
|
58
|
+
def app
|
59
|
+
build_controller do
|
60
|
+
path 'foo' do
|
61
|
+
url('bar')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
response_for { get '/foo' }.body.should == "/foo/bar"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#root_url' do
|
71
|
+
it "returns the controller's root URL" do
|
72
|
+
def app
|
73
|
+
root_url_printer = build_controller do
|
74
|
+
path 'bar' do
|
75
|
+
"My root URL is still #{root_url}"
|
76
|
+
end
|
77
|
+
|
78
|
+
"My root URL is #{root_url}"
|
79
|
+
end
|
80
|
+
|
81
|
+
build_controller do
|
82
|
+
path 'foo' do
|
83
|
+
run root_url_printer
|
84
|
+
end
|
85
|
+
|
86
|
+
run root_url_printer
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
response_for { get '/' }.body.should == 'My root URL is /'
|
91
|
+
response_for { get '/foo' }.body.should == 'My root URL is /foo'
|
92
|
+
response_for { get '/foo/bar' }.body.should == 'My root URL is still /foo'
|
93
|
+
end
|
94
|
+
|
95
|
+
it "appends extra paths to the root URL if provided" do
|
96
|
+
def app
|
97
|
+
some_controller = build_controller do
|
98
|
+
root_url('bar')
|
99
|
+
end
|
100
|
+
|
101
|
+
build_controller do
|
102
|
+
path 'foo' do
|
103
|
+
run some_controller
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
response_for { get '/foo' }.body.should == "/foo/bar"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
35
112
|
end
|
36
113
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: happy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.
|
4
|
+
version: 0.1.0.pre10
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2012-06-02 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
requirement: &
|
16
|
+
requirement: &70168378480640 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3.1'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70168378480640
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rack
|
27
|
-
requirement: &
|
27
|
+
requirement: &70168378479020 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,21 +32,21 @@ dependencies:
|
|
32
32
|
version: '1.4'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70168378479020
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: happy-helpers
|
38
|
-
requirement: &
|
38
|
+
requirement: &70168378476360 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 0.1.0.
|
43
|
+
version: 0.1.0.pre8
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70168378476360
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: allowance
|
49
|
-
requirement: &
|
49
|
+
requirement: &70168378462860 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 0.1.1
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70168378462860
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: happy-cli
|
60
|
-
requirement: &
|
60
|
+
requirement: &70168378462140 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: 0.1.0.pre1
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70168378462140
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rake
|
71
|
-
requirement: &
|
71
|
+
requirement: &70168378461380 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70168378461380
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: rspec
|
82
|
-
requirement: &
|
82
|
+
requirement: &70168378460640 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ~>
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: '2.8'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70168378460640
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: rspec-html-matchers
|
93
|
-
requirement: &
|
93
|
+
requirement: &70168378459800 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ! '>='
|
@@ -98,10 +98,10 @@ dependencies:
|
|
98
98
|
version: '0'
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *70168378459800
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: rack-test
|
104
|
-
requirement: &
|
104
|
+
requirement: &70168378458420 !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
107
|
- - ! '>='
|
@@ -109,10 +109,10 @@ dependencies:
|
|
109
109
|
version: '0'
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
|
-
version_requirements: *
|
112
|
+
version_requirements: *70168378458420
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: watchr
|
115
|
-
requirement: &
|
115
|
+
requirement: &70168378454200 !ruby/object:Gem::Requirement
|
116
116
|
none: false
|
117
117
|
requirements:
|
118
118
|
- - ! '>='
|
@@ -120,7 +120,7 @@ dependencies:
|
|
120
120
|
version: '0'
|
121
121
|
type: :development
|
122
122
|
prerelease: false
|
123
|
-
version_requirements: *
|
123
|
+
version_requirements: *70168378454200
|
124
124
|
description: A happy little toolkit for writing web applications.
|
125
125
|
email:
|
126
126
|
- hendrik@mans.de
|