link2 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jonas Grimfelt
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,211 @@
1
+ h1. LINK²
2
+
3
+ _Generation next *link_to*-helper for Rails: Spiced with intelligence, and semantic beauty._
4
+
5
+ h2. Introduction
6
+
7
+ A better link helper for Rails designed with the principle of making smart assumptions based on what's known to avoid repeated and unnecessary code declarations; and at the same time making the code more semantic/readable/beautiful (my subjective opinion). On top of that - for even more maintainable views - scoped I18n translations without lean defaults for fast prototyping. WIN-WIN-WIN.
8
+
9
+ This is *not* a re-implementation of @link_to@/@button_to@-helpers; rather it wraps these but parses the specified method arguments and extracts as much known information as possible to fill in the missing pieces. The core helpers are not altered at all: You can call them old-school like there was no tomorrow (after using Link2 for a while you'll spoiled and the core helpers will feel so yesterday, really).
10
+
11
+ *Goals/Features:*
12
+
13
+ * Rapid prototyping-friendly link helpers - with no trade-offs, really.
14
+ * Based on specified arguments: Use what's know to make smart assumptions => DRYer - and more maintainable view code.
15
+ * Use power of I18n: Lookup scoped translations based on action, model, etc., for more flexible translations - with lean defaults. Enhanced with some nifty interpolation features as well. Code first, translate later.
16
+ * Full test-coverage for stability.
17
+ * Well-documented code.
18
+
19
+ h2. Dependencies
20
+
21
+ * "rails 2.3.x":http://github.com/rails/rails only. Might as well work with Rails 3 already, but I didn't write that.
22
+
23
+ For testing: test-unit, and "left-right":http://github.com/jordi/leftright.
24
+
25
+ h2. Installation
26
+
27
+ "Drop a *Gem* on 'em":http://open.spotify.com/track/2pqKuHtn8ZKMMMqbJrA2e7:
28
+
29
+ <pre>
30
+ $ sudo gem install link2
31
+ </pre>
32
+
33
+ h2. Setup
34
+
35
+ Generate initializer (optional):
36
+
37
+ <pre>
38
+ $ ./script/generate link2
39
+ </pre>
40
+
41
+ h2. Usage
42
+
43
+ *A few examples* using the spiced up @link_to@/@button_to@ helpers, using our trusty fellow *Post* class:
44
+
45
+ <pre>
46
+ link "No operation"
47
+ # => link_to 'No operation', '#'
48
+
49
+ link "Hilarious", 'http://bash.org'
50
+ # => link_to 'No operation', 'http://bash.org'
51
+
52
+ link 'http://bash.org'
53
+ # => link_to 'http://bash.org', 'http://bash.org'
54
+
55
+ link :home
56
+ # => link_to I18n.t(:home, ...), root_path
57
+
58
+ link :back
59
+ # => link_to I18n.t(:home, ...), :back
60
+
61
+ link @post
62
+ # => link_to I18n.t(:show, ...), post_path(@post)
63
+
64
+ link [@post, @comment]
65
+ # => link_to I18n.t(:show, ...), post_comment_path(@post, @comment)
66
+
67
+ link :new, :post
68
+ # => link_to I18n.t(:new, ...), new_post_path
69
+
70
+ link :new, Post
71
+ # => link_to I18n.t(:new, ...), new_post_path
72
+
73
+ link :new, @post
74
+ # => link_to I18n.t(:new, ...), new_post_path(:id => @post.id) # ...if you think about it; useful for cloning.
75
+
76
+ link :edit, @post
77
+ # => link_to I18n.t(:edit, ...), edit_post_path(@post)
78
+
79
+ link :edit, [@post, @comment]
80
+ # => link_to I18n.t(:edit, ...), edit_post_comment_path(@post, @comment)
81
+
82
+ link :kick, @post
83
+ # => link_to I18n.t(:kick, ...), kick_post_path(@post)
84
+
85
+ link "New one!", :new, Post
86
+ # => link_to "New one!", new_post_path
87
+
88
+ link :new, UserSession { image_tag('sign_in_button.png') }
89
+ # => link_to image_tag('sign_in_button.png'), new_user_session_path
90
+
91
+ ...
92
+ </pre>
93
+
94
+ ...and for the record (not yet implemented, but soon):
95
+
96
+ <pre>
97
+ link :edit, [@post, @comment]
98
+ # => ::Link2::NotImplementedYetError, "case link(:action, [...]) not yet supported. Need to refactor some stuff."
99
+
100
+ link :new
101
+ # => ::Link2::NotImplementedYetError, "Auto-detection of resource is not supported yet."
102
+ </pre>
103
+
104
+ Same works for @button_to@, and you also can optionally use the branded aliases: @link2@ and @button2@.
105
+
106
+ h2. Options hashes: URL Options + HTML Options
107
+
108
+ Link2 link helpers accept options in the same way as the core helpers @link_to@/@button_to@: first @options@ (a.k.a. @url_options@) and then @html_options@. See the "Rails core UrlHelpers documentation":http://railsapi.com/doc/rails-v2.3.5/classes/ActionView/Helpers/UrlHelper.html#M002452 for details on this. Link2 helpers just pass any non-Link2-related options to the Rails core helpers. In other words no need to learn a new API; just pass the needed options like in the past.
109
+
110
+ h2. Expected arguments
111
+
112
+ A summary of the expected argument flavors if the examples for the curious minds:
113
+
114
+ <pre>
115
+ link(label, options = {}, html_options = {})
116
+ link(url, options = {}, html_options = {}, &content_block)
117
+ link(resource, options = {}, html_options = {}, &content_block)
118
+
119
+ link(label, url, options = {}, html_options = {})
120
+ link(action, resource, options = {}, html_options = {}, &content_block)
121
+
122
+ link(label, action, resource)
123
+ </pre>
124
+
125
+ Same applies to the @button@ helper, naturally.
126
+
127
+ h2. I18n
128
+
129
+ Link2 was designed with the power of I18n in mind; following certain lookup patterns to make it easier to manage link-translations even as your Rails-app grows. This is how in short:
130
+
131
+ *1. Lookup scopes* (Optional)
132
+
133
+ This is the default lookup order, in priority order:
134
+
135
+ <pre>
136
+ Link2.setup do |config|
137
+ config.i18n_scopes = [
138
+ '{{models}}.links.{{action}}',
139
+ 'links.{{action}}'
140
+ ]
141
+ end
142
+ </pre>
143
+
144
+ Valid lookup scope interpolations:
145
+
146
+ * @model@ - link model name, e.g. CaptainMorgan / @captain_morgan => "captain_morgan"
147
+ * @models@ - pluralized link model name, e.g. CaptainMorgan / @captain_morgan => "captain_morgans"
148
+ * @controller@ - current controller name
149
+ * @action@ - the link action name
150
+
151
+ *2. Translations* (Optional)
152
+
153
+ <pre>
154
+ en:
155
+ links:
156
+ order: "Bartender!!"
157
+ drink: "Drink your {{resource}} now"
158
+ captain_morgans:
159
+ links:
160
+ order: "New barrel of rum with lime, ohoy!"
161
+ drink: "Slurp {{name}} like a pirate"
162
+ </pre>
163
+
164
+ Valid value interpolations:
165
+
166
+ * @resource@ - resource humanized name (parsed with I18n if possible), e.g. CaptainMorgan / @captain_morgan => "captain morgan"
167
+ * @resources@ - pluralized resource humanized name (parsed with I18n if possible), e.g. CaptainMorgan / @captain_morgan => "captain morgans"
168
+ * @name@ - current resource name to_s-value, e.g. @captain_morgan.to_s => "Captain Morgan with Cola and lime #4"
169
+
170
+ *3. Label parsing* (Optional)
171
+
172
+ Customize @to_s@ for your model(s) to return a more humane string.
173
+
174
+ <pre>
175
+ class CaptainMorgan < ActiveRecord::Base
176
+ def to_s
177
+ "Captain ##{self.id}"
178
+ end
179
+ end
180
+ </pre>
181
+
182
+ *4. Go*
183
+
184
+ Now - with the config and translations setup - let the unicorn free:
185
+
186
+ <pre>
187
+ link :order, Caipirinha
188
+ # => link_to "Bartender!!", new_caipirinha_path
189
+
190
+ link :drink, @sour_caipirinha
191
+ # => link_to "Drink your caipirinha now", drink_caipirinha_path(@sour_caipirinha)
192
+
193
+ link :order, CaptainMorgan
194
+ # => link_to "New barrel of rum with lime, ohoy!", new_captain_morgan_path
195
+
196
+ link :drink, @captain_morgan_no_8
197
+ # => link_to "Slurp Captain #8 like a pirate", drink_captain_morgan_path(@captain_morgan_no_8) # See: CaptainMorgan#to_s
198
+ </pre>
199
+
200
+ h2. TODO
201
+
202
+ See "TODO":http://github.com/grimen/link2/blob/master/TODO
203
+
204
+ h2. A note on design decisions
205
+
206
+ I didn't extend the behavior for the helper @link_to_if@, @link_to_unless@ because I simply think they should not be used in Rails apps; my strong opinion is that they introduce unnecessary complexity in code and make it less readable. I also skipped @link_to_if_current@ because it's not very thorough implementation - even an extra URI slash make it confused, which is funny. Hate it, or LOVE IT.
207
+
208
+ h2. License
209
+
210
+ Released under the MIT license.
211
+ Copyright (c) "Jonas Grimfelt":http://github.com/grimen
data/Rakefile ADDED
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+ require File.join(File.dirname(__FILE__), 'lib', 'link2', 'version')
7
+
8
+ # Gem managment tasks.
9
+ #
10
+ # == Generate gemspec, build & install locally:
11
+ #
12
+ # rake gemspec
13
+ # rake build
14
+ # sudo rake install
15
+ #
16
+ # == Git tag & push to origin/master
17
+ #
18
+ # rake release
19
+ #
20
+ # == Release to Gemcutter.org:
21
+ #
22
+ # rake gemcutter:release
23
+ #
24
+ begin
25
+ gem 'jeweler'
26
+ require 'jeweler'
27
+
28
+ Jeweler::Tasks.new do |spec|
29
+ spec.name = "link2"
30
+ spec.version = ::Link2::VERSION
31
+ spec.summary = %{Generation next link_to-helper for Rails: Spiced with intelligence, and semantic beauty.}
32
+ spec.description = spec.summary
33
+ spec.homepage = "http://github.com/grimen/#{spec.name}"
34
+ spec.authors = ["Jonas Grimfelt"]
35
+ spec.email = "grimen@gmail.com"
36
+
37
+ spec.files = FileList["[A-Z]*", File.join(*%w[{generators,lib,rails} ** *]).to_s]
38
+
39
+ spec.add_dependency 'activesupport', '>= 2.3.0'
40
+ spec.add_dependency 'actionpack', '>= 2.3.0'
41
+
42
+ spec.add_development_dependency 'test-unit', '= 1.2.3'
43
+ spec.add_development_dependency 'activerecord', '>= 2.3.0'
44
+ end
45
+
46
+ Jeweler::GemcutterTasks.new
47
+ rescue LoadError
48
+ puts "Jeweler - or one of its dependencies - is not available. " <<
49
+ "Install it with: sudo gem install jeweler -s http://gemcutter.org"
50
+ end
51
+
52
+ desc 'Default: run unit tests.'
53
+ task :default => :test
54
+
55
+ desc 'Test the plugin.'
56
+ Rake::TestTask.new(:test) do |test|
57
+ test.libs = ['lib', 'test']
58
+ test.pattern = 'test/**/*_test.rb'
59
+ test.verbose = true
60
+ end
61
+
62
+ desc 'Generate documentation for the plugin.'
63
+ Rake::RDocTask.new(:rdoc) do |rdoc|
64
+ rdoc.rdoc_dir = 'rdoc'
65
+ rdoc.title = ''
66
+ rdoc.options << '--line-numbers' << '--inline-source'
67
+ rdoc.rdoc_files.include('README')
68
+ rdoc.rdoc_files.include('init.rb')
69
+ rdoc.rdoc_files.include('lib/**/*.rb')
70
+ end
data/TODO ADDED
@@ -0,0 +1,49 @@
1
+ TODO
2
+
3
+ * Implement the problematic one: link(:action, [@parent, @resource])
4
+
5
+ * Write tests for options hashes as well: url_options vs. html_options
6
+
7
+ * Generate proper DOM ID selectors, e.g. class="new post"
8
+
9
+ DOM Selectors
10
+
11
+ To make a web-designer's life easier Link2 generates some semantic selector classes based on specified/known link properties for easier DOM-manipulation with CSS and javascript:
12
+
13
+ <pre>
14
+ link :new, Post
15
+ # => <a class="new post" ...>...<a/>
16
+
17
+ link :edit, @post_14
18
+ # => <a id="edit_post_14_action" class="edit post" ...>...<a/>
19
+
20
+ link :back
21
+ # => <a class="back" ...>...<a/>
22
+ </pre>
23
+
24
+ * Fix action mapping procs to handle named scopes. Should work, but do not for some reason. =/
25
+
26
+ * Fix action mapping procs to receive session, params, etc in a options hash. My mocking/stubbing don't want to work. ='(
27
+
28
+ * Allow customization of alternatives to Object#to_s for parsing object label, i.e. {{name}} interpolation.
29
+ * Collections detection:
30
+
31
+ link(:index) => link(@posts) => link(:index, @posts) => link("Index", @posts)
32
+
33
+ * Auto-detect current resource instance for member views (non-collection)
34
+
35
+ link(:edit) => link(:edit, @post)
36
+
37
+ Detect using "self.controller_name.singularize", or use "resource" if InheritedResources is used.
38
+
39
+ * Add support: #link_to_function + #button_to_function: I18n, more?
40
+
41
+ js_link(:hello, "alert('Clicked :hello.');") => link_to_function(I18n.t(:hello, ...), "alert('Clicked :hello.');")
42
+
43
+ * Add support: #link_to_remote + #button_to_remote: I18n, more? Could probably be cleaned up quite a lot.
44
+
45
+ ajax_link(...)
46
+
47
+ Consider:
48
+
49
+ * Possible parse TITLE-attribute from the model, say Object#link_title or similar.
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ class Link2Generator < Rails::Generator::Base
4
+
5
+ def manifest
6
+ record do |m|
7
+ m.template 'initializer.rb', File.join(*%w[config initializers link2.rb])
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ if defined?(::Link2)
4
+ ::Link2.setup do |config|
5
+ # Configure how - and in what order - link labels should be looked up.
6
+ # config.i18n_scopes = ['links.{{action}}']
7
+ #
8
+ # Configure any custom action mappings.
9
+ # config.action_mappings = {
10
+ # :home => lambda { root_path },
11
+ # :back => lambda { |url| url || session[:return_to] || :back }
12
+ # }
13
+ end
14
+ end
data/lib/link2.rb ADDED
@@ -0,0 +1,115 @@
1
+ # encoding: utf-8
2
+ begin
3
+ require 'active_support'
4
+ rescue LoadError
5
+ gem 'activesupport'
6
+ require 'active_support'
7
+ end
8
+
9
+ begin
10
+ require 'action_view'
11
+ rescue LoadError
12
+ gem 'actionpack'
13
+ require 'action_view'
14
+ end
15
+
16
+ module Link2
17
+
18
+ autoload :Brain, 'link2/brain'
19
+ autoload :Helpers, 'link2/helpers'
20
+ autoload :I18n, 'link2/i18n'
21
+ autoload :Support, 'link2/support'
22
+ autoload :VERSION, 'link2/version'
23
+
24
+ # include ::ActionController::UrlWriter
25
+
26
+ # Default URL value; if none can be assumed. Useful value for prototyping.
27
+ DEFAULT_LINK = '#'
28
+
29
+ # Default lookup scope if none is set.
30
+ DEFAULT_I18N_SCOPE = [:links]
31
+
32
+ # Default I18n lookup scopes if none are set.
33
+ DEFAULT_I18N_SCOPES = [
34
+ '{{resource}}.links.{{action}}',
35
+ 'links.{{action}}'
36
+ ]
37
+
38
+ Error = Class.new(::StandardError)
39
+ NotImplementedYetError = Class.new(::NotImplementedError)
40
+
41
+ # TODO: Make aware of named routes.
42
+ # include ::ActionController::UrlWriter # don't work as expected here (isolated run), but works in Rails app =S
43
+ #
44
+ # Don't work even if Rails API docs tells so... 8(
45
+ # http://api.rubyonrails.org/classes/ActionController/UrlWriter.html
46
+ # ActionController::UrlWriter.root_path
47
+
48
+ # Default action mappings: Link value shortcuts sort of.
49
+ # FIXME:
50
+ # DEFAULT_ACTION_MAPPINGS = {
51
+ # :home => lambda { '/' }, # TODO: Allow named routes, e.g. root_path
52
+ # :back => lambda { |url| url || options[:session][:return_to] || :back }
53
+ # }
54
+ DEFAULT_ACTION_MAPPINGS = {
55
+ :home => lambda { '/' }, # TODO: Allow named routes, e.g. root_path
56
+ :back => lambda { |url| url || :back }
57
+ }
58
+
59
+ mattr_accessor :i18n_scope
60
+ @@i18n_scope = DEFAULT_I18N_SCOPE
61
+
62
+ # I18n lookup scopes in ascending order of priority.
63
+ # Used for scoped I18n translations based on model, action, etc.,
64
+ # for flexability.
65
+ mattr_accessor :i18n_scopes
66
+ @@i18n_scopes = DEFAULT_I18N_SCOPES
67
+
68
+ # Action mappings - a.k.a. "link value shortcuts" - that should
69
+ # be recognized.
70
+ mattr_accessor :action_mappings
71
+ @@action_mappings = DEFAULT_ACTION_MAPPINGS
72
+
73
+ class << self
74
+
75
+ # Yield self for configuration block:
76
+ #
77
+ # Link2.setup do |config|
78
+ # config.i18n_scope = [:actions]
79
+ # end
80
+ #
81
+ def setup
82
+ yield(self)
83
+ end
84
+
85
+ # Finds any existing "action mapping" based on a mapping key (custom action).
86
+ #
87
+ # == Example/Usage:
88
+ #
89
+ # Link2.action_mappings[:back] = lambda { |url| url || session[:return_to] || :back }
90
+ #
91
+ # url_for_mapping(:back)
92
+ # # => session[:return_to] || :back
93
+ #
94
+ # url_for_mapping(:back, "/unicorns")
95
+ # # => "/unicorns"
96
+ #
97
+ def url_for_mapping(action, custom_url = nil, options = {})
98
+ expression = ::Link2.action_mappings[action]
99
+ if expression.is_a?(Proc)
100
+ expression.arity == 0 ? expression.call : expression.call(custom_url)
101
+ else
102
+ expression
103
+ end
104
+ end
105
+
106
+ end
107
+ end
108
+
109
+ # Make I18n aware of our default locale.
110
+ I18n.load_path.unshift File.expand_path(File.join(File.dirname(__FILE__), 'link2', 'locales', 'en.yml'))
111
+
112
+ # Add extended ActionView behaviour.
113
+ ActionView::Base.class_eval do
114
+ include ::Link2::Helpers
115
+ end