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 CHANGED
@@ -1,29 +1,136 @@
1
- # Happy
1
+ # Happy Ruby
2
2
 
3
- TODO: Write a gem description
3
+ **The Happy Web Application Toolkit for Ruby.**
4
4
 
5
- ## Installation
5
+ ## Introduction
6
6
 
7
- Add this line to your application's Gemfile:
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
- gem 'happy'
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
- And then execute:
11
+ ### Examples
12
12
 
13
- $ bundle
13
+ "Hello world" with Happy:
14
14
 
15
- Or install it yourself as:
15
+ ```ruby
16
+ # config.ru
17
+ require 'happy'
16
18
 
17
- $ gem install happy
19
+ class MyApp < Happy::Controller
20
+ route do
21
+ 'Hello world'
22
+ end
23
+ end
18
24
 
19
- ## Usage
25
+ run MyApp
26
+ ```
20
27
 
21
- TODO: Write usage instructions here
28
+ How about something a little bit closer to reality?
22
29
 
23
- ## Contributing
30
+ ``` ruby
31
+ # config.ru
32
+ require 'happy'
24
33
 
25
- 1. Fork it
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
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.pre6'
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
@@ -31,7 +31,7 @@ module Happy
31
31
  name.scan(/:(\w+)/).flatten.each do |var|
32
32
  request.params[var] = path_match.captures.shift
33
33
  end
34
- remaining_path.shift
34
+ previous_path << remaining_path.shift
35
35
  end
36
36
 
37
37
  serve! instance_exec(&blk)
@@ -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, :remaining_path, :params, :session,
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Happy
2
- VERSION = "0.1.0.pre9"
2
+ VERSION = "0.1.0.pre10"
3
3
  end
@@ -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.pre9
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: &70100544312640 !ruby/object:Gem::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: *70100544312640
24
+ version_requirements: *70168378480640
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rack
27
- requirement: &70100544261560 !ruby/object:Gem::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: *70100544261560
35
+ version_requirements: *70168378479020
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: happy-helpers
38
- requirement: &70100544255780 !ruby/object:Gem::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.pre6
43
+ version: 0.1.0.pre8
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70100544255780
46
+ version_requirements: *70168378476360
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: allowance
49
- requirement: &70100544253560 !ruby/object:Gem::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: *70100544253560
57
+ version_requirements: *70168378462860
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: happy-cli
60
- requirement: &70100544251160 !ruby/object:Gem::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: *70100544251160
68
+ version_requirements: *70168378462140
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
- requirement: &70100544249920 !ruby/object:Gem::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: *70100544249920
79
+ version_requirements: *70168378461380
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: rspec
82
- requirement: &70100544248940 !ruby/object:Gem::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: *70100544248940
90
+ version_requirements: *70168378460640
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: rspec-html-matchers
93
- requirement: &70100544247880 !ruby/object:Gem::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: *70100544247880
101
+ version_requirements: *70168378459800
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: rack-test
104
- requirement: &70100544246800 !ruby/object:Gem::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: *70100544246800
112
+ version_requirements: *70168378458420
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: watchr
115
- requirement: &70100544245520 !ruby/object:Gem::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: *70100544245520
123
+ version_requirements: *70168378454200
124
124
  description: A happy little toolkit for writing web applications.
125
125
  email:
126
126
  - hendrik@mans.de