cells 3.10.1 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|