cells 3.10.1 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +7 -0
- data/CHANGES.md +18 -0
- data/Gemfile +1 -1
- data/README.md +182 -57
- data/TODO.md +9 -0
- data/cells.gemspec +3 -1
- data/gemfiles/Gemfile.rails4-0 +2 -1
- data/gemfiles/Gemfile.rails4-1 +5 -3
- data/lib/cell/base.rb +33 -64
- data/lib/cell/base/prefixes.rb +27 -0
- data/lib/cell/base/self_contained.rb +13 -0
- data/lib/cell/base/view.rb +15 -0
- data/lib/cell/builder.rb +52 -53
- data/lib/cell/caching.rb +21 -4
- data/lib/cell/concept.rb +85 -0
- data/lib/cell/rails.rb +13 -6
- data/lib/cell/rails/view_model.rb +47 -6
- data/lib/cell/rendering.rb +13 -13
- data/lib/cell/test_case.rb +3 -3
- data/lib/cell/view_model.rb +151 -0
- data/lib/cells.rb +0 -60
- data/lib/cells/rails.rb +10 -5
- data/lib/cells/railtie.rb +2 -5
- data/lib/cells/version.rb +1 -1
- data/lib/generators/erb/concept_generator.rb +17 -0
- data/lib/generators/haml/concept_generator.rb +17 -0
- data/lib/generators/rails/concept_generator.rb +16 -0
- data/lib/generators/templates/concept/cell.rb +9 -0
- data/lib/generators/templates/concept/view.erb +7 -0
- data/lib/generators/templates/concept/view.haml +4 -0
- data/lib/generators/trailblazer/base.rb +21 -0
- data/lib/generators/trailblazer/view_generator.rb +18 -0
- data/test/app/cells/bassist_cell.rb +1 -1
- data/test/app/cells/record/views/layout.haml +2 -0
- data/test/app/cells/record/views/show.erb +1 -0
- data/test/app/cells/record/views/song.erb +1 -0
- data/test/app/cells/song/scale.haml +1 -0
- data/test/cell_module_test.rb +18 -37
- data/test/cells_module_test.rb +1 -1
- data/test/concept_generator_test.rb +26 -0
- data/test/concept_test.rb +78 -0
- data/test/dummy/Rakefile +1 -1
- data/test/dummy/app/assets/javascripts/application.js +2 -0
- data/test/dummy/app/cells/album/assets/album.js +1 -0
- data/test/dummy/app/concepts/song/assets/songs.js +1 -0
- data/test/dummy/config/application.rb +11 -6
- data/test/dummy/label/label.gemspec +2 -2
- data/test/helper_test.rb +2 -2
- data/test/prefixes_test.rb +75 -0
- data/test/rails/asset_pipeline_test.rb +20 -0
- data/test/rails/caching_test.rb +1 -9
- data/test/rails/cells_test.rb +2 -17
- data/test/rails/forms_test.rb +2 -1
- data/test/rails/integration_test.rb +199 -8
- data/test/rails/render_test.rb +2 -10
- data/test/rails/view_model_test.rb +135 -60
- data/test/rails_helper_api_test.rb +2 -1
- data/test/self_contained_test.rb +9 -2
- data/test/test_case_test.rb +2 -2
- data/test/test_helper.rb +2 -3
- metadata +117 -33
- checksums.yaml +0 -7
- data/test/dummy/app/controllers/musician_controller.rb +0 -36
- data/test/rails/router_test.rb +0 -45
data/lib/cell/test_case.rb
CHANGED
@@ -96,7 +96,7 @@ module Cell
|
|
96
96
|
# assert_select html, "h1", "The latest and greatest!"
|
97
97
|
def render_cell(name, state, *args)
|
98
98
|
# DISCUSS: should we allow passing a block here, just as in controllers?
|
99
|
-
@subject_cell = ::Cell::Rails.
|
99
|
+
@subject_cell = ::Cell::Rails.cell_for(name, @controller, *args)
|
100
100
|
@view_assigns = extract_state_ivars_for(@subject_cell) do
|
101
101
|
@last_invoke = @subject_cell.render_state(state, *args)
|
102
102
|
end
|
@@ -110,7 +110,7 @@ module Cell
|
|
110
110
|
# Example:
|
111
111
|
# assert_equal "Doo Dumm Dumm..." cell(:bassist).play
|
112
112
|
def cell(name, *args, &block)
|
113
|
-
Cell::Rails.
|
113
|
+
Cell::Rails.cell_for(name, @controller, *args).tap do |cell|
|
114
114
|
cell.instance_eval &block if block_given?
|
115
115
|
end
|
116
116
|
end
|
@@ -150,7 +150,7 @@ module Cell
|
|
150
150
|
@last_invoke = self.class.controller_class.new(@controller).render_state(state, *args)
|
151
151
|
end
|
152
152
|
|
153
|
-
if Cell.
|
153
|
+
if Cell.rails_version.~("4.0", "4.1")
|
154
154
|
include ActiveSupport::Testing::ConstantLookup
|
155
155
|
def self.determine_default_controller_class(name) # FIXME: fix that in Rails 4.x.
|
156
156
|
determine_constant_from_test_name(name) do |constant|
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# no helper_method calls
|
2
|
+
# no instance variables
|
3
|
+
# no locals
|
4
|
+
# options are automatically made instance methods via constructor.
|
5
|
+
# call "helpers" in class
|
6
|
+
|
7
|
+
# TODO: warn when using ::property but not passing in model in constructor.
|
8
|
+
|
9
|
+
class Cell::ViewModel < Cell::Rails
|
10
|
+
abstract!
|
11
|
+
|
12
|
+
include Cell::OptionsConstructor
|
13
|
+
#include ActionView::Helpers::UrlHelper
|
14
|
+
include ActionView::Context # this includes CompiledTemplates, too.
|
15
|
+
# properties :title, :body
|
16
|
+
attr_reader :model
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
module Helpers
|
21
|
+
# DISCUSS: highest level API method. add #cell here.
|
22
|
+
def collection(name, controller, array, method=:show)
|
23
|
+
# FIXME: this is the problem in Concept cells, we don't wanna call Cell::Rails.cell_for here.
|
24
|
+
array.collect { |model| cell_for(name, controller, model).call(method) }.join("\n").html_safe
|
25
|
+
end
|
26
|
+
|
27
|
+
# TODO: this should be in Helper or something. this should be the only entry point from controller/view.
|
28
|
+
def cell(name, controller, *args, &block) # classic Rails fuzzy API.
|
29
|
+
if args.first.is_a?(Hash) and array = args.first[:collection]
|
30
|
+
return collection(name, controller, array)
|
31
|
+
end
|
32
|
+
|
33
|
+
cell_for(name, controller, *args, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
extend Helpers # FIXME: do we really need ViewModel::cell/::collection ?
|
37
|
+
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def property(*names)
|
41
|
+
delegate *names, :to => :model
|
42
|
+
end
|
43
|
+
|
44
|
+
include Helpers
|
45
|
+
end
|
46
|
+
|
47
|
+
def cell(name, *args)
|
48
|
+
self.class.cell(name, parent_controller, *args)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def initialize(*args)
|
53
|
+
super
|
54
|
+
_prepare_context # happens in AV::Base at the bottom.
|
55
|
+
end
|
56
|
+
|
57
|
+
def render(options={})
|
58
|
+
if options.is_a?(Hash)
|
59
|
+
options.reverse_merge!(:view => state_for_implicit_render)
|
60
|
+
else
|
61
|
+
options = {:view => options.to_s}
|
62
|
+
end
|
63
|
+
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
67
|
+
def call(state=:show)
|
68
|
+
# it is ok to call to_s.html_safe here as #call is a defined rendering method.
|
69
|
+
# DISCUSS: IN CONCEPT: render( view: implicit_state)
|
70
|
+
render_state(state).to_s.html_safe
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def view_context
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def state_for_implicit_render()
|
79
|
+
caller[1].match(/`(\w+)/)[1]
|
80
|
+
end
|
81
|
+
|
82
|
+
# def implicit_state
|
83
|
+
# controller_path.split("/").last
|
84
|
+
# end
|
85
|
+
|
86
|
+
|
87
|
+
# FIXME: this module is to fix a design flaw in Rails 4.0. the problem is that AV::UrlHelper mixes in the wrong #url_for.
|
88
|
+
# if we could mix in everything else from the helper except for the #url_for, it would be fine.
|
89
|
+
module LinkToHelper
|
90
|
+
include ActionView::Helpers::TagHelper
|
91
|
+
|
92
|
+
def link_to(name = nil, options = nil, html_options = nil, &block)
|
93
|
+
html_options, options, name = options, name, block if block_given?
|
94
|
+
options ||= {}
|
95
|
+
|
96
|
+
html_options = convert_options_to_data_attributes(options, html_options)
|
97
|
+
|
98
|
+
url = url_for(options)
|
99
|
+
html_options['href'] ||= url
|
100
|
+
|
101
|
+
content_tag(:a, name || url, html_options, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def convert_options_to_data_attributes(options, html_options)
|
105
|
+
if html_options
|
106
|
+
html_options = html_options.stringify_keys
|
107
|
+
html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options)
|
108
|
+
|
109
|
+
disable_with = html_options.delete("disable_with")
|
110
|
+
confirm = html_options.delete('confirm')
|
111
|
+
method = html_options.delete('method')
|
112
|
+
|
113
|
+
if confirm
|
114
|
+
message = ":confirm option is deprecated and will be removed from Rails 4.1. " \
|
115
|
+
"Use 'data: { confirm: \'Text\' }' instead."
|
116
|
+
ActiveSupport::Deprecation.warn message
|
117
|
+
|
118
|
+
html_options["data-confirm"] = confirm
|
119
|
+
end
|
120
|
+
|
121
|
+
add_method_to_attributes!(html_options, method) if method
|
122
|
+
|
123
|
+
if disable_with
|
124
|
+
message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \
|
125
|
+
"Use 'data: { disable_with: \'Text\' }' instead."
|
126
|
+
ActiveSupport::Deprecation.warn message
|
127
|
+
|
128
|
+
html_options["data-disable-with"] = disable_with
|
129
|
+
end
|
130
|
+
|
131
|
+
html_options
|
132
|
+
else
|
133
|
+
link_to_remote_options?(options) ? {'data-remote' => 'true'} : {}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def link_to_remote_options?(options)
|
138
|
+
if options.is_a?(Hash)
|
139
|
+
options.delete('remote') || options.delete(:remote)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# FIXME: fix that in rails core.
|
145
|
+
if Cell.rails_version.~("4.0", "4.1")
|
146
|
+
include LinkToHelper
|
147
|
+
else
|
148
|
+
include ActionView::Helpers::UrlHelper
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
data/lib/cells.rb
CHANGED
@@ -1,63 +1,3 @@
|
|
1
|
-
# = Cells
|
2
|
-
#
|
3
|
-
# Cells are view components for Rails. Being lightweight controllers with actions and views, cells are the
|
4
|
-
# answer to <tt>DoubleRenderError</tt>s and the long awaited ability to render actions within actions.
|
5
|
-
#
|
6
|
-
# == Directory structure
|
7
|
-
#
|
8
|
-
# Cells live in +app/cells/+ and have a similar file layout as controllers.
|
9
|
-
#
|
10
|
-
# app/
|
11
|
-
# cells/
|
12
|
-
# shopping_cart_cell.rb
|
13
|
-
# shopping_cart/
|
14
|
-
# status.html.erb
|
15
|
-
# product_list.haml
|
16
|
-
# layouts/
|
17
|
-
# box.html.erb
|
18
|
-
#
|
19
|
-
# == Cell nesting
|
20
|
-
#
|
21
|
-
# Is is good practice to split up complex cell views into multiple states or views. Remember, you can always use
|
22
|
-
# <tt>render :view => ...</tt> and <tt>render :state => ...</tt> in your views.
|
23
|
-
#
|
24
|
-
# Following this, you stick to encapsulation and save your code from getting inscrutable, as it happens in most
|
25
|
-
# controller views, partials, and so called "helpers".
|
26
|
-
#
|
27
|
-
# Given the following setup:
|
28
|
-
#
|
29
|
-
# class ShoppingCartCell < Cell::Base
|
30
|
-
# def cart
|
31
|
-
# @items = items_in_cart
|
32
|
-
# render
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# def order_button
|
36
|
-
# render
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# You could now render the "Order!" button in the +cart.haml+ view.
|
40
|
-
#
|
41
|
-
# - for item in @items
|
42
|
-
# = @item.title
|
43
|
-
#
|
44
|
-
# render :state => :order_button
|
45
|
-
#
|
46
|
-
# which is more than just a partial, as you may execute additional code in the state method.
|
47
|
-
#
|
48
|
-
# == View inheritance
|
49
|
-
#
|
50
|
-
# Unlike controllers, Cells can form a class hierarchy. Even views are inherited, which is pretty useful
|
51
|
-
# when overriding only small parts of the view.
|
52
|
-
#
|
53
|
-
# So if you'd need a special "Order!" button with sparkling stars on christmas, your cell would go like this.
|
54
|
-
#
|
55
|
-
# class XmasCartCell < ShoppingCartCell
|
56
|
-
# end
|
57
|
-
#
|
58
|
-
# Beside your new class you'd provide a star-sprangled button view in +xmas_cart/order_button.haml+.
|
59
|
-
# When rendering the +cart+ state, the states as well as the "missing" views are inherited from ancesting cells,
|
60
|
-
# this is pretty DRY and object-oriented, isn't it?
|
61
1
|
module Cells
|
62
2
|
# Setup your special needs for Cells here. Use this to add new view paths.
|
63
3
|
#
|
data/lib/cells/rails.rb
CHANGED
@@ -4,10 +4,16 @@ module Cells
|
|
4
4
|
module Rails
|
5
5
|
module ActionController
|
6
6
|
def cell_for(name, *args, &block)
|
7
|
+
return Cell::Rails::ViewModel.cell(name, self, *args, &block) if args.first.is_a?(Hash) and args.first[:collection] # FIXME: we only want this feature in view models for now.
|
7
8
|
::Cell::Base.cell_for(name, self, *args, &block)
|
8
9
|
end
|
9
10
|
alias_method :cell, :cell_for # DISCUSS: make this configurable?
|
10
11
|
|
12
|
+
def concept_for(name, *args, &block)
|
13
|
+
return Cell::Concept.cell(name, self, *args, &block)
|
14
|
+
end
|
15
|
+
alias_method :concept, :concept_for
|
16
|
+
|
11
17
|
# Renders the cell state and returns the content. You may pass options here, too. They will be
|
12
18
|
# around in @opts.
|
13
19
|
#
|
@@ -42,11 +48,6 @@ module Cells
|
|
42
48
|
#
|
43
49
|
# will expire the view for state <tt>:display_list</tt> in the cell <tt>MyListingCell</tt>.
|
44
50
|
def expire_cell_state(cell_class, state, args={}, opts=nil)
|
45
|
-
if cell_class.is_a?(Symbol)
|
46
|
-
ActiveSupport::Deprecation.warn "Please pass the cell class into #expire_cell_state, as in expire_cell_state(DirectorCell, :count, :user_id => 1)"
|
47
|
-
cell_class = Cell::Rails.class_from_cell_name(cell_class)
|
48
|
-
end
|
49
|
-
|
50
51
|
key = cell_class.state_cache_key(state, args)
|
51
52
|
cell_class.expire_cache_key(key, opts)
|
52
53
|
end
|
@@ -66,6 +67,10 @@ module Cells
|
|
66
67
|
def render_cell(name, state, *args, &block)
|
67
68
|
::Cell::Rails.render_cell_for(name, state, controller, *args, &block)
|
68
69
|
end
|
70
|
+
|
71
|
+
def concept(name, *args, &block)
|
72
|
+
controller.concept_for(name, *args, &block)
|
73
|
+
end
|
69
74
|
end
|
70
75
|
end
|
71
76
|
end
|
data/lib/cells/railtie.rb
CHANGED
@@ -7,14 +7,10 @@ module Cells
|
|
7
7
|
|
8
8
|
initializer "cells.attach_router" do |app|
|
9
9
|
Cell::Base.class_eval do
|
10
|
-
include app.routes.url_helpers
|
10
|
+
include app.routes.url_helpers # TODO: i hate this, make it better in Rails.
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
initializer "cells.setup_view_paths" do |app|
|
15
|
-
Cell::Base.setup_view_paths!
|
16
|
-
end
|
17
|
-
|
18
14
|
initializer "cells.setup_engines_view_paths" do |app|
|
19
15
|
Cells::Engines.append_engines_view_paths_for(app.config.action_controller)
|
20
16
|
end
|
@@ -24,6 +20,7 @@ module Cells
|
|
24
20
|
(app.config.cells.with_assets or []).each do |name|
|
25
21
|
# FIXME: this doesn't take engine cells into account.
|
26
22
|
app.config.assets.paths << "#{app.root}/app/cells/#{name}/assets"
|
23
|
+
app.config.assets.paths << "#{app.root}/app/concepts/#{name}/assets" # TODO: find out type.
|
27
24
|
end
|
28
25
|
end
|
29
26
|
|
data/lib/cells/version.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'generators/trailblazer/view_generator'
|
2
|
+
|
3
|
+
module Erb
|
4
|
+
module Generators
|
5
|
+
class ConceptGenerator < ::Trailblazer::Generators::ViewGenerator
|
6
|
+
|
7
|
+
source_root File.expand_path('../../templates/concept', __FILE__)
|
8
|
+
|
9
|
+
private
|
10
|
+
def handler
|
11
|
+
:erb
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'generators/trailblazer/view_generator'
|
2
|
+
|
3
|
+
module Haml
|
4
|
+
module Generators
|
5
|
+
class ConceptGenerator < ::Trailblazer::Generators::ViewGenerator
|
6
|
+
|
7
|
+
source_root File.expand_path('../../templates/concept', __FILE__)
|
8
|
+
|
9
|
+
private
|
10
|
+
def handler
|
11
|
+
:haml
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'generators/trailblazer/base'
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module Generators
|
5
|
+
class ConceptGenerator < ::Trailblazer::Generators::Cell
|
6
|
+
source_root File.expand_path('../../templates/concept', __FILE__)
|
7
|
+
|
8
|
+
def create_cell_file
|
9
|
+
template 'cell.rb', "#{base_path}/cell.rb"
|
10
|
+
end
|
11
|
+
|
12
|
+
hook_for(:template_engine)
|
13
|
+
hook_for(:test_framework)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/named_base'
|
3
|
+
|
4
|
+
module Trailblazer
|
5
|
+
module Generators
|
6
|
+
class Cell < ::Rails::Generators::NamedBase
|
7
|
+
class_option :template_engine
|
8
|
+
class_option :test_framework
|
9
|
+
class_option :base_cell_class, :type => :string, :default => "Cell::Rails"
|
10
|
+
class_option :base_cell_path
|
11
|
+
|
12
|
+
argument :actions, :type => :array, :default => [:show], :banner => "action action"
|
13
|
+
|
14
|
+
private
|
15
|
+
def base_path
|
16
|
+
path = (options[:base_cell_path] || 'app/concepts').to_s
|
17
|
+
File.join(path, class_path, file_name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
module Generators
|
3
|
+
class ViewGenerator < Cell # Trailblazer::Generators::Cell
|
4
|
+
def create_views
|
5
|
+
for state in actions do
|
6
|
+
@state = state
|
7
|
+
@path = File.join(base_path, "views/#{state}.#{handler}") #base_path defined in Cells::Generators::Base.
|
8
|
+
template "view.#{handler}", @path
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def handler
|
14
|
+
raise "Please implement #handler in your view generator and return something like `:erb`."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|