blockpile 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.rdoc +137 -0
- data/VERSION +1 -1
- data/blockpile.gemspec +7 -5
- data/lib/blockpile/base.rb +16 -4
- data/lib/blockpile/paths.rb +15 -0
- data/lib/blockpile/setup.rb +6 -6
- data/lib/blockpile.rb +4 -1
- metadata +7 -5
- data/README +0 -13
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/*
|
data/README.rdoc
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
== Blockpile
|
2
|
+
|
3
|
+
Piles of extendable rails view blocks, triggered and masked behind simple helpers
|
4
|
+
|
5
|
+
== What?
|
6
|
+
|
7
|
+
Sometimes within rails, view logic can become complex. As complexity begins to increase, a common theme or pattern occurs:
|
8
|
+
|
9
|
+
* View logic begins to grow overly complex for what makes sense in view templates. Lengthy, confusing, and downright dirty views cause you to loose sleep.
|
10
|
+
* Logic can be broken out of the view templates and placed into the controller.
|
11
|
+
* Eventually, when the view logic is needed in another request, the only solution is to pull the entire logic into a helper method.
|
12
|
+
|
13
|
+
So what's the problem?
|
14
|
+
|
15
|
+
Rails view helpers are great for view logic, and especially for small reusable view components. Unfortunately, complex view logic is almost always a rats nest of logic and string interpolation. In fact, I'm still not sure how this has become a viable solution for rails developers, as it seems to go against what I understand to be the "rails way". To their defense, there simply isn't any other way. Until now!
|
16
|
+
|
17
|
+
To better illustrate the problem, I decided to insert a snippet of a rails view helper from a popular project management tool "Redmine":
|
18
|
+
|
19
|
+
def render_project_hierarchy(projects)
|
20
|
+
s = ''
|
21
|
+
if projects.any?
|
22
|
+
ancestors = []
|
23
|
+
projects.each do |project|
|
24
|
+
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
|
25
|
+
s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
|
26
|
+
else
|
27
|
+
ancestors.pop
|
28
|
+
s << "</li>"
|
29
|
+
while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
|
30
|
+
ancestors.pop
|
31
|
+
s << "</ul></li>\n"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
classes = (ancestors.empty? ? 'root' : 'child')
|
35
|
+
s << "<li class='#{classes}'><div class='#{classes}'>" +
|
36
|
+
link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
|
37
|
+
s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
|
38
|
+
s << "</div>\n"
|
39
|
+
ancestors << project
|
40
|
+
end
|
41
|
+
s << ("</li></ul>\n" * ancestors.size)
|
42
|
+
end
|
43
|
+
s
|
44
|
+
end
|
45
|
+
|
46
|
+
So even if you are talented enough to understand what this code is doing, let me ask 2 questions:
|
47
|
+
|
48
|
+
* Do you mind if we upgrade the markup language to fit a new design?
|
49
|
+
* Even though this is an acceptable solution, does it really feel good to have this nest under your hood?
|
50
|
+
|
51
|
+
Before we move on, let me add that I'm not trashing the Redmine code-base. In fact, I chose an example from this project simply because the author is fantastic and this example mirrors at least one helper method in every rails app.
|
52
|
+
|
53
|
+
== Enter Blockpile
|
54
|
+
|
55
|
+
A blockpile solves this particular problem by slicing the view layer into 2 finer layers: logic, and template. It is no different than a controller/template relationship, except that a blockpile can be easily distributed across the application just as a helper method can.
|
56
|
+
|
57
|
+
From a high level, a blockpile consists of 3 components:
|
58
|
+
|
59
|
+
* A Ruby Class file
|
60
|
+
* A template file (erb only for now, others coming soon)
|
61
|
+
* A function call to include the blockpile in your existing templates
|
62
|
+
|
63
|
+
== Example
|
64
|
+
|
65
|
+
To keep this example simple, I'll show you one way to clean up the example above. Since I'm lazy, I'm not actually going to fix the example above but show the concept which could be filled in later.
|
66
|
+
|
67
|
+
=== Ruby Class file
|
68
|
+
# app/helpers/blockpiles/project_hierarchy.erb
|
69
|
+
|
70
|
+
class ProjectHierarchy < Blockpile::Base
|
71
|
+
|
72
|
+
def build(options={})
|
73
|
+
# use this instead of initialize
|
74
|
+
@projects = options[:project]
|
75
|
+
prepare_hierarchy
|
76
|
+
end
|
77
|
+
|
78
|
+
def prepare_hierarchy
|
79
|
+
# fill in hierarchy here
|
80
|
+
end
|
81
|
+
|
82
|
+
def projects
|
83
|
+
# fill in code
|
84
|
+
end
|
85
|
+
|
86
|
+
def project_children(project)
|
87
|
+
# could return project children
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
Here we can take care of all logic, in preparation for the view. We need not interpolate, as the template file will pull data from this class instance when needed. Here we can focus on the beauty and elegance of Ruby, and not on the mixture of presentation.
|
93
|
+
|
94
|
+
=== A template file
|
95
|
+
|
96
|
+
# app/views/blockpiles/project_hierarchy.html.erb
|
97
|
+
|
98
|
+
|
99
|
+
<ul>
|
100
|
+
<% projects.each do |project| %>
|
101
|
+
|
102
|
+
<li><%= project.name %></li>
|
103
|
+
|
104
|
+
<% project_children(project).each do |child| %>
|
105
|
+
<li class="child"><%= child.name %></li>
|
106
|
+
<% end %>
|
107
|
+
|
108
|
+
<% end %>
|
109
|
+
</ul>
|
110
|
+
|
111
|
+
The purpose of the template file is to "pull" logic from the Blockpile Class. We are assuming all logic will be taken care of in the class, and here we can simply focus on our view presentation, using only conditional logic (if else etc).
|
112
|
+
|
113
|
+
=== helper-like function call
|
114
|
+
|
115
|
+
# app/views/{controller}/{action}.html.erb
|
116
|
+
|
117
|
+
<%= project_hierarchy :project => @project %>
|
118
|
+
|
119
|
+
There is no boiler plate for turning your Blockpile Class into a helper method, it happens under the hood. Notice we are passing @project, that will be available in your class through the options hash in the build function. The input of the builder method is completely up to you. In this example I chose to use an "options" hash. You could replace that with 3 input arguments instead if you prefer, in which case you would simply pass @project instead of assigning to a hash key.
|
120
|
+
|
121
|
+
=== Summary
|
122
|
+
|
123
|
+
In essence, what could be a horrible nest of string interpolation and logic, can become a clean isolation of logic and markup language. I realize this sample solution is lacking, but I'll leave the actual logic to the developer.
|
124
|
+
|
125
|
+
=== Generator
|
126
|
+
|
127
|
+
You can quickly create a Blockpile Class and Template pair by using the generator:
|
128
|
+
|
129
|
+
rails generate blockpile project_hierarchy
|
130
|
+
|
131
|
+
And thats it! You now have a Blockpile ready for use, with a helper method called "project_hierarchy".
|
132
|
+
|
133
|
+
== Customize your piles
|
134
|
+
|
135
|
+
Coming soon.
|
136
|
+
|
137
|
+
Copyright (c) 2010 Tyler Flint, released under the MIT license
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/blockpile.gemspec
CHANGED
@@ -5,24 +5,26 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{blockpile}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Tyler Flint"]
|
12
|
-
s.date = %q{2010-08-
|
12
|
+
s.date = %q{2010-08-16}
|
13
13
|
s.description = %q{This module attempts to create structured view helpers. Essentially, a blockpile consists of a ruby class file, and a template. This allows for isolated blocks of view logic, that can maintain a clean separation of markup language from ruby code. Blocks can be inherited from to DRY up view logic.}
|
14
14
|
s.email = %q{tylerflint@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
|
-
"README"
|
16
|
+
"README.rdoc"
|
17
17
|
]
|
18
18
|
s.files = [
|
19
|
-
"
|
20
|
-
"
|
19
|
+
".gitignore",
|
20
|
+
"MIT-LICENSE",
|
21
|
+
"README.rdoc",
|
21
22
|
"Rakefile",
|
22
23
|
"VERSION",
|
23
24
|
"blockpile.gemspec",
|
24
25
|
"lib/blockpile.rb",
|
25
26
|
"lib/blockpile/base.rb",
|
27
|
+
"lib/blockpile/paths.rb",
|
26
28
|
"lib/blockpile/setup.rb",
|
27
29
|
"lib/generators/blockpile/USAGE",
|
28
30
|
"lib/generators/blockpile/blockpile_generator.rb",
|
data/lib/blockpile/base.rb
CHANGED
@@ -9,7 +9,7 @@ class Blockpile::Base
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def to_html
|
12
|
-
render_template
|
12
|
+
render_template
|
13
13
|
end
|
14
14
|
|
15
15
|
def build
|
@@ -19,9 +19,21 @@ class Blockpile::Base
|
|
19
19
|
protected
|
20
20
|
|
21
21
|
# Assumes /views/helper/ as base
|
22
|
-
def render_template
|
23
|
-
|
24
|
-
|
22
|
+
def render_template
|
23
|
+
ERB.new( File.read( get_template ) ).result binding
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_template
|
27
|
+
get_paths.each do |path|
|
28
|
+
if File::exists?( path + "/" + @template + ".html.erb")
|
29
|
+
return path + "/" + @template + ".html.erb"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
raise "Unable to find template for this blockpile"
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_paths
|
36
|
+
Blockpile::Paths.get_paths
|
25
37
|
end
|
26
38
|
|
27
39
|
def method_missing(*args, &block)
|
data/lib/blockpile/setup.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
module Blockpile
|
2
2
|
module Setup
|
3
3
|
def self.add_load_path(path)
|
4
|
-
|
5
|
-
|
4
|
+
Dir.glob(path + "/**/*.rb") do |file|
|
5
|
+
ActiveSupport::Dependencies.autoload_paths << File.dirname(file)
|
6
6
|
file_name = file.split(/\//).pop.gsub(/.rb/, '')
|
7
|
-
class_name = file_name.classify
|
7
|
+
class_name = file_name.classify
|
8
8
|
ActionView::Base.class_eval %{
|
9
9
|
def #{file_name}(*args, &block)
|
10
|
-
|
11
|
-
yield
|
12
|
-
raw
|
10
|
+
blockpile = #{class_name}.new(self, session, params, '#{file_name}', *args)
|
11
|
+
yield blockpile if block_given?
|
12
|
+
raw blockpile.to_html
|
13
13
|
end
|
14
14
|
}
|
15
15
|
end
|
data/lib/blockpile.rb
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
require 'rails'
|
2
2
|
require 'blockpile/setup'
|
3
3
|
require 'blockpile/base'
|
4
|
+
require 'blockpile/paths'
|
4
5
|
|
5
6
|
module Blockpile
|
6
7
|
class Railtie < Rails::Railtie
|
7
8
|
initializer "blockpile.setup default directories" do
|
8
9
|
Blockpile.setup do |config|
|
9
|
-
config.add_load_path Rails.root.to_s + '/app/helpers/blockpiles
|
10
|
+
config.add_load_path Rails.root.to_s + '/app/helpers/blockpiles'
|
10
11
|
end
|
12
|
+
Blockpile::Paths.add_template_path Rails.root.to_s + '/app/views/blockpiles/'
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
16
|
def self.setup
|
15
17
|
yield Blockpile::Setup
|
16
18
|
end
|
19
|
+
|
17
20
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 3
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.3.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Tyler Flint
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-08-
|
17
|
+
date: 2010-08-16 00:00:00 -06:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
@@ -25,15 +25,17 @@ executables: []
|
|
25
25
|
extensions: []
|
26
26
|
|
27
27
|
extra_rdoc_files:
|
28
|
-
- README
|
28
|
+
- README.rdoc
|
29
29
|
files:
|
30
|
+
- .gitignore
|
30
31
|
- MIT-LICENSE
|
31
|
-
- README
|
32
|
+
- README.rdoc
|
32
33
|
- Rakefile
|
33
34
|
- VERSION
|
34
35
|
- blockpile.gemspec
|
35
36
|
- lib/blockpile.rb
|
36
37
|
- lib/blockpile/base.rb
|
38
|
+
- lib/blockpile/paths.rb
|
37
39
|
- lib/blockpile/setup.rb
|
38
40
|
- lib/generators/blockpile/USAGE
|
39
41
|
- lib/generators/blockpile/blockpile_generator.rb
|