cells 3.11.3 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +15 -13
- data/Appraisals +23 -0
- data/CHANGES.md +20 -3
- data/Gemfile +5 -7
- data/README.md +317 -236
- data/Rakefile +1 -1
- data/TODO.md +3 -0
- data/cells.gemspec +19 -28
- data/gemfiles/rails3.2.gemfile +14 -0
- data/gemfiles/rails4.0.gemfile +14 -0
- data/gemfiles/rails4.1.gemfile +15 -0
- data/gemfiles/rails4.2.gemfile +14 -0
- data/lib/cell.rb +28 -21
- data/lib/cell/caching.rb +10 -22
- data/lib/cell/caching/notification.rb +15 -0
- data/lib/cell/concept.rb +4 -69
- data/lib/cell/development.rb +11 -0
- data/lib/{cells → cell}/engines.rb +1 -1
- data/lib/cell/layout.rb +20 -0
- data/lib/cell/partial.rb +17 -0
- data/lib/cell/{base/prefixes.rb → prefixes.rb} +1 -1
- data/lib/cell/rails.rb +58 -50
- data/lib/cell/railtie.rb +60 -0
- data/lib/cell/{base/self_contained.rb → self_contained.rb} +1 -1
- data/lib/cell/templates.rb +60 -0
- data/lib/cell/test_case.rb +4 -162
- data/lib/cell/testing.rb +15 -0
- data/lib/cell/twin.rb +11 -29
- data/lib/cell/version.rb +10 -0
- data/lib/cell/view_model.rb +196 -88
- data/lib/cells.rb +1 -20
- data/lib/rails/generators/cell/cell_generator.rb +43 -0
- data/lib/rails/generators/cell/templates/cell.rb.erb +8 -0
- data/lib/{generators/templates/concept → rails/generators/cell/templates}/view.erb +0 -0
- data/lib/{generators/templates/concept → rails/generators/cell/templates}/view.haml +0 -0
- data/lib/rails/generators/cell/templates/view.slim +2 -0
- data/lib/rails/generators/concept/concept_generator.rb +38 -0
- data/lib/{generators/templates/concept/cell.rb → rails/generators/concept/templates/concept.rb.erb} +2 -2
- data/lib/{generators → rails/generators/concept}/templates/view.erb +0 -0
- data/lib/{generators → rails/generators/concept}/templates/view.haml +0 -0
- data/lib/rails/generators/concept/templates/view.slim +2 -0
- data/lib/rails/generators/test_unit/cell/cell_generator.rb +21 -0
- data/lib/{generators/templates/cell_test.rb → rails/generators/test_unit/cell/templates/unit_test.rb.erb} +3 -3
- data/lib/rails/generators/test_unit/concept/concept_generator.rb +21 -0
- data/lib/rails/generators/test_unit/concept/templates/unit_test.rb.erb +11 -0
- data/lib/{cells → tasks}/cells.rake +0 -0
- data/test/builder_test.rb +58 -0
- data/test/caching_test.rb +298 -0
- data/test/cell_benchmark.rb +32 -0
- data/test/cell_generator_test.rb +51 -82
- data/test/cell_test.rb +8 -23
- data/test/concept_generator_test.rb +22 -13
- data/test/concept_test.rb +41 -75
- data/test/dummy/app/views/musician/hamlet.html.erb +1 -0
- data/test/dummy/config/application.rb +21 -8
- data/test/{app/cells/bassist/play.html.erb → fixtures/bassist/play.erb} +0 -0
- data/test/fixtures/concepts/record/views/layout.erb +1 -0
- data/test/{app → fixtures}/concepts/record/views/show.erb +0 -0
- data/test/{app → fixtures}/concepts/record/views/song.erb +0 -0
- data/test/fixtures/inherit_views_test/popper/tap.erb +1 -0
- data/test/fixtures/inherit_views_test/tapper/play.erb +1 -0
- data/test/fixtures/inherit_views_test/tapper/tap.erb +1 -0
- data/test/fixtures/partial_test/with_partial/show.erb +1 -0
- data/test/fixtures/partials/_show.html.erb +1 -0
- data/test/fixtures/partials/_show.xml.erb +1 -0
- data/test/fixtures/song/ivar.erb +1 -0
- data/test/fixtures/song/show.erb +1 -0
- data/test/fixtures/song/with_erb.erb +4 -0
- data/test/fixtures/song/with_html.erb +1 -0
- data/test/fixtures/song/with_locals.erb +2 -0
- data/test/fixtures/song_with_layout/happy.erb +1 -0
- data/test/fixtures/song_with_layout/merry.erb +1 -0
- data/test/fixtures/song_with_layout/show.erb +1 -0
- data/test/fixtures/song_with_layout/show_with_layout.erb +1 -0
- data/test/fixtures/templates_caching_test/song/show.erb +1 -0
- data/test/fixtures/url_helper_test/song/edit.erb +8 -0
- data/test/fixtures/url_helper_test/song/with_block.erb +2 -0
- data/test/fixtures/url_helper_test/song/with_capture.erb +4 -0
- data/test/fixtures/url_helper_test/song/with_content_tag.erb +6 -0
- data/test/fixtures/url_helper_test/song/with_form_for_block.erb +3 -0
- data/test/fixtures/url_helper_test/song/with_link_to.erb +3 -0
- data/test/layout_test.rb +57 -0
- data/test/partial_test.rb +27 -0
- data/test/prefixes_test.rb +36 -10
- data/test/public_test.rb +42 -0
- data/test/rails_extensions_test.rb +51 -0
- data/test/render_test.rb +103 -0
- data/test/templates_test.rb +45 -0
- data/test/test_case_test.rb +21 -122
- data/test/test_helper.rb +37 -33
- data/test/twin_test.rb +3 -7
- data/test/url_helper_test.rb +89 -0
- metadata +92 -357
- data/gemfiles/Gemfile.rails3-0 +0 -7
- data/gemfiles/Gemfile.rails3-1 +0 -7
- data/gemfiles/Gemfile.rails3-2 +0 -7
- data/gemfiles/Gemfile.rails4-0 +0 -12
- data/gemfiles/Gemfile.rails4-1 +0 -12
- data/lib/cell/base.rb +0 -82
- data/lib/cell/base/view.rb +0 -15
- data/lib/cell/builder.rb +0 -71
- data/lib/cell/deprecations.rb +0 -41
- data/lib/cell/dsl.rb +0 -7
- data/lib/cell/rack.rb +0 -32
- data/lib/cell/rails/helper_api.rb +0 -37
- data/lib/cell/rails/view_model.rb +0 -159
- data/lib/cell/rails3_0_strategy.rb +0 -82
- data/lib/cell/rails3_1_strategy.rb +0 -40
- data/lib/cell/rails4_0_strategy.rb +0 -39
- data/lib/cell/rails4_1_strategy.rb +0 -40
- data/lib/cell/rendering.rb +0 -109
- data/lib/cells/rails.rb +0 -86
- data/lib/cells/railtie.rb +0 -38
- data/lib/cells/version.rb +0 -3
- data/lib/generators/USAGE +0 -30
- data/lib/generators/cells/base.rb +0 -22
- data/lib/generators/cells/cell_generator.rb +0 -15
- data/lib/generators/cells/view_generator.rb +0 -18
- data/lib/generators/erb/cell_generator.rb +0 -15
- data/lib/generators/erb/concept_generator.rb +0 -17
- data/lib/generators/haml/cell_generator.rb +0 -17
- data/lib/generators/haml/concept_generator.rb +0 -17
- data/lib/generators/rails/cell_generator.rb +0 -16
- data/lib/generators/rails/concept_generator.rb +0 -16
- data/lib/generators/slim/cell_generator.rb +0 -17
- data/lib/generators/templates/cell.rb +0 -9
- data/lib/generators/templates/view.slim +0 -4
- data/lib/generators/test_unit/cell_generator.rb +0 -14
- data/lib/generators/trailblazer/base.rb +0 -21
- data/lib/generators/trailblazer/view_generator.rb +0 -18
- data/test/app/cells/album/views/cover.haml +0 -1
- data/test/app/cells/bad_guitarist/_dii.html.erb +0 -1
- data/test/app/cells/bad_guitarist_cell.rb +0 -2
- data/test/app/cells/bassist/_dii.html.erb +0 -1
- data/test/app/cells/bassist/ahem.html.erb +0 -1
- data/test/app/cells/bassist/compose.html.erb +0 -1
- data/test/app/cells/bassist/contact_form.html.erb +0 -1
- data/test/app/cells/bassist/form_for.erb +0 -3
- data/test/app/cells/bassist/form_for_in_haml.haml +0 -2
- data/test/app/cells/bassist/jam.html.erb +0 -3
- data/test/app/cells/bassist/play.js.erb +0 -1
- data/test/app/cells/bassist/pose.html.erb +0 -1
- data/test/app/cells/bassist/promote.html.erb +0 -1
- data/test/app/cells/bassist/provoke.html.erb +0 -1
- data/test/app/cells/bassist/shout.html.erb +0 -1
- data/test/app/cells/bassist/sing.html.haml +0 -1
- data/test/app/cells/bassist/slap.html.erb +0 -1
- data/test/app/cells/bassist/yell.en.html.erb +0 -1
- data/test/app/cells/bassist_cell.rb +0 -25
- data/test/app/cells/club_security.rb +0 -2
- data/test/app/cells/club_security/guard/help.html.erb +0 -1
- data/test/app/cells/club_security/guard_cell.rb +0 -6
- data/test/app/cells/club_security/medic/help.html.erb +0 -1
- data/test/app/cells/club_security/medic_cell.rb +0 -8
- data/test/app/cells/layouts/b.erb +0 -1
- data/test/app/cells/layouts/metal.html.erb +0 -1
- data/test/app/cells/rails_helper_api_test/bassist/edit.html.erb +0 -5
- data/test/app/cells/shouter/sing.html.erb +0 -1
- data/test/app/cells/song/dashboard.haml +0 -7
- data/test/app/cells/song/details.html.haml +0 -1
- data/test/app/cells/song/info.html.haml +0 -1
- data/test/app/cells/song/lyrics.html.haml +0 -6
- data/test/app/cells/song/plays.haml +0 -1
- data/test/app/cells/song/scale.haml +0 -1
- data/test/app/cells/song/show.html.haml +0 -3
- data/test/app/cells/song/title.html.haml +0 -1
- data/test/app/cells/trumpeter/promote.html.erb +0 -1
- data/test/app/cells/trumpeter_cell.rb +0 -8
- data/test/app/cells/view_model_test/comments/show.haml +0 -7
- data/test/app/concepts/record/views/layout.haml +0 -2
- data/test/app/views/shared/_dong.html.erb +0 -1
- data/test/cell_module_test.rb +0 -170
- data/test/cells_module_test.rb +0 -27
- data/test/deprecations_test.rb +0 -101
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/views/musician/hamlet.html.haml +0 -1
- data/test/dummy/config/environments/development.rb +0 -16
- data/test/dummy/config/environments/production.rb +0 -46
- data/test/dummy/config/environments/test.rb +0 -33
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/label/app/cells/label/show.erb +0 -1
- data/test/dummy/label/app/cells/label_cell.rb +0 -5
- data/test/dummy/label/label.gemspec +0 -20
- data/test/dummy/label/lib/label.rb +0 -4
- data/test/dummy/label/lib/label/version.rb +0 -3
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -26
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
- data/test/helper_test.rb +0 -81
- data/test/rack_test.rb +0 -32
- data/test/rails/asset_pipeline_test.rb +0 -20
- data/test/rails/caching_test.rb +0 -456
- data/test/rails/cells_test.rb +0 -119
- data/test/rails/forms_test.rb +0 -75
- data/test/rails/integration_test.rb +0 -299
- data/test/rails/render_test.rb +0 -189
- data/test/rails/view_model_test.rb +0 -226
- data/test/rails/view_test.rb +0 -49
- data/test/rails_helper_api_test.rb +0 -58
- data/test/self_contained_test.rb +0 -31
data/lib/cell/railtie.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
begin
|
2
|
+
require 'rails/railtie'
|
3
|
+
rescue LoadError
|
4
|
+
else
|
5
|
+
module Cell
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
require 'cell/rails'
|
8
|
+
config.cells = ActiveSupport::OrderedOptions.new
|
9
|
+
|
10
|
+
initializer('cells.attach_router') do |app|
|
11
|
+
ViewModel.class_eval do
|
12
|
+
include app.routes.url_helpers # TODO: i hate this, make it better in Rails.
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
initializer 'cells.template_engine' do |app|
|
17
|
+
ViewModel.template_engine = app.config.app_generators.rails.fetch(:template_engine, 'erb').to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
# ruthlessly stolen from the zurb-foundation gem.
|
21
|
+
initializer 'cells.update_asset_paths' do |app|
|
22
|
+
Array(app.config.cells.with_assets).each do |name|
|
23
|
+
# FIXME: this doesn't take engine cells into account.
|
24
|
+
app.config.assets.paths.append "#{app.root}/app/cells/#{name}/assets"
|
25
|
+
app.config.assets.paths.append "#{app.root}/app/concepts/#{name}/assets" # TODO: find out type.
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
initializer('cells.rails_extensions') do |app|
|
30
|
+
ActiveSupport.on_load(:action_controller) do
|
31
|
+
self.class_eval do
|
32
|
+
include ::Cell::RailsExtensions::ActionController
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
ActiveSupport.on_load(:action_view) do
|
37
|
+
self.class_eval do
|
38
|
+
include ::Cell::RailsExtensions::ActionView
|
39
|
+
end
|
40
|
+
|
41
|
+
#include assert helpers (image_path, font_path, ect)
|
42
|
+
ViewModel.class_eval do
|
43
|
+
include ActionView::Helpers::AssetTagHelper
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
initializer('cells.development') do |app|
|
49
|
+
if Rails.env == "development"
|
50
|
+
require "cell/development"
|
51
|
+
ViewModel.send(:include, Development)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
rake_tasks do
|
56
|
+
load 'tasks/cells.rake'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Cell
|
2
|
+
# Gets cached in production.
|
3
|
+
class Templates
|
4
|
+
# prefixes could be instance variable as they will never change.
|
5
|
+
def [](bases, prefixes, view, engine, formats=nil)
|
6
|
+
base = bases.first # FIXME.
|
7
|
+
|
8
|
+
find_template(base, prefixes, view, engine)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def cache
|
14
|
+
@cache ||= Cache.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_template(base, prefixes, view, engine)
|
18
|
+
view = "#{view}.#{engine}"
|
19
|
+
|
20
|
+
cache.fetch(prefixes, view) do |prefix|
|
21
|
+
# this block is run once per cell class per process, for each prefix/view tuple.
|
22
|
+
create(base, prefix, view)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def create(base, prefix, view)
|
27
|
+
# puts "...checking #{base}/#{prefix}/#{view}"
|
28
|
+
return unless File.exists?("#{base}/#{prefix}/#{view}") # DISCUSS: can we use Tilt.new here?
|
29
|
+
Tilt.new("#{base}/#{prefix}/#{view}", :escape_html => false, :escape_attrs => false)
|
30
|
+
end
|
31
|
+
|
32
|
+
# {["comment/row/views", comment/views"][show.haml] => "Tpl:comment/view/show.haml"}
|
33
|
+
class Cache
|
34
|
+
def initialize
|
35
|
+
@store = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Iterates prefixes and yields block. Returns and caches when block returned template.
|
39
|
+
# Note that it caches per prefixes set as this will most probably never change.
|
40
|
+
def fetch(prefixes, view)
|
41
|
+
template = get(prefixes, view) and return template # cache hit.
|
42
|
+
|
43
|
+
prefixes.find do |prefix|
|
44
|
+
template = yield(prefix) and return store(prefixes, view, template)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
# ["comment/views"] => "show.haml"
|
50
|
+
def get(prefixes, view)
|
51
|
+
@store[prefixes] ||= {}
|
52
|
+
@store[prefixes][view]
|
53
|
+
end
|
54
|
+
|
55
|
+
def store(prefix, view, template)
|
56
|
+
@store[prefix][view] = template # the nested hash is always present here.
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/cell/test_case.rb
CHANGED
@@ -1,168 +1,10 @@
|
|
1
|
+
require "cell/testing"
|
2
|
+
|
1
3
|
module Cell
|
2
4
|
# Test your cells.
|
3
5
|
#
|
4
|
-
#
|
5
|
-
# to infer the tested cell name from the test name if you use declarative testing. You can also set it
|
6
|
-
# with TestCase.tests.
|
7
|
-
#
|
8
|
-
# A declarative test would look like
|
9
|
-
#
|
10
|
-
# class SellOutTest < Cell::TestCase
|
11
|
-
# tests ShoppingCartCell
|
12
|
-
#
|
13
|
-
# it "should be rendered nicely" do
|
14
|
-
# invoke :order_button, :items => @fixture_items
|
15
|
-
#
|
16
|
-
# assert_select "button", "Order now!"
|
17
|
-
# end
|
18
|
-
#
|
19
|
-
# You can also do stuff yourself, like
|
20
|
-
#
|
21
|
-
# it "should be rendered even nicer" do
|
22
|
-
# html = render_cell(:shopping_cart, :order_button, , :items => @fixture_items)
|
23
|
-
# assert_selector "button", "Order now!", html
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# Or even unit test your cell:
|
27
|
-
#
|
28
|
-
# it "should provide #default_items" do
|
29
|
-
# assert_equal [@item1, @item2], cell(:shopping_cart).default_items
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# == Test helpers
|
33
|
-
#
|
34
|
-
# Basically, we got these new methods:
|
35
|
-
#
|
36
|
-
# +invoke+:: Renders the passed +state+ with your tested cell. You may pass options like in #render_cell.
|
37
|
-
# +render_cell+:: As in your views. Will return the rendered view.
|
38
|
-
# +assert_selector+:: Like #assert_select except that the last argument is the html markup you wanna test.
|
39
|
-
# +cell+:: Gives you a cell instance for unit testing and stuff.
|
6
|
+
# TODO: document me, Capybara, etc.
|
40
7
|
class TestCase < ActiveSupport::TestCase
|
41
|
-
|
42
|
-
# Invokes assert_select for the last argument, the +content+ string.
|
43
|
-
#
|
44
|
-
# Example:
|
45
|
-
# assert_selector "h1", "The latest and greatest!", "<h1>The latest and greatest!</h1>"
|
46
|
-
#
|
47
|
-
# would be true.
|
48
|
-
def assert_selector(*args, &block)
|
49
|
-
rails_assert_select(HTML::Document.new(args.pop).root, *args, &block)
|
50
|
-
end
|
51
|
-
|
52
|
-
# Invokes assert_select on the markup set by the last #invoke.
|
53
|
-
#
|
54
|
-
# Example:
|
55
|
-
# invoke :latest
|
56
|
-
# assert_select "h1", "The latest and greatest!"
|
57
|
-
def assert_select(*args, &block)
|
58
|
-
super(HTML::Document.new(last_invoke).root, *args, &block)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
module CommonTestMethods
|
63
|
-
def setup
|
64
|
-
@controller ||= Class.new(ActionController::Base).new
|
65
|
-
@request ||= ::ActionController::TestRequest.new
|
66
|
-
@response = ::ActionController::TestResponse.new
|
67
|
-
@controller.request = @request
|
68
|
-
@controller.response = @response
|
69
|
-
@controller.params = {}
|
70
|
-
end
|
71
|
-
|
72
|
-
# Runs the block while computing the instance variables diff from before and after.
|
73
|
-
def extract_state_ivars_for(cell)
|
74
|
-
before = cell.instance_variables
|
75
|
-
yield
|
76
|
-
after = cell.instance_variables
|
77
|
-
|
78
|
-
Hash[(after - before).collect do |var|
|
79
|
-
next if var =~ /^@_/
|
80
|
-
[var[1, var.length].to_sym, cell.instance_variable_get(var)]
|
81
|
-
end.compact]
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
|
86
|
-
module TestMethods
|
87
|
-
include CommonTestMethods
|
88
|
-
|
89
|
-
attr_reader :last_invoke, :subject_cell, :view_assigns
|
90
|
-
|
91
|
-
# Use this for functional tests of your application cells.
|
92
|
-
#
|
93
|
-
# Example:
|
94
|
-
# should "spit out a h1 title" do
|
95
|
-
# html = render_cell(:news, :latest)
|
96
|
-
# assert_select html, "h1", "The latest and greatest!"
|
97
|
-
def render_cell(name, state, *args)
|
98
|
-
# DISCUSS: should we allow passing a block here, just as in controllers?
|
99
|
-
@subject_cell = ::Cell::Rails.cell_for(name, @controller, *args)
|
100
|
-
@view_assigns = extract_state_ivars_for(@subject_cell) do
|
101
|
-
@last_invoke = @subject_cell.render_state(state, *args)
|
102
|
-
end
|
103
|
-
|
104
|
-
@last_invoke
|
105
|
-
end
|
106
|
-
|
107
|
-
# Builds an instance of <tt>name</tt>Cell for unit testing.
|
108
|
-
# Passes the optional block to <tt>cell.instance_eval</tt>.
|
109
|
-
#
|
110
|
-
# Example:
|
111
|
-
# assert_equal "Doo Dumm Dumm..." cell(:bassist).play
|
112
|
-
def cell(name, *args, &block)
|
113
|
-
Cell::Rails.cell_for(name, @controller, *args).tap do |cell|
|
114
|
-
cell.instance_eval &block if block_given?
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# Execute the passed +block+ in a real view context of +cell_class+.
|
119
|
-
# Usually you'd test helpers here.
|
120
|
-
#
|
121
|
-
# Example:
|
122
|
-
#
|
123
|
-
# assert_equal("<h1>Modularity rocks.</h1>", in_view do content_tag(:h1, "Modularity rocks."))
|
124
|
-
def in_view(cell_class, &block)
|
125
|
-
subject = cell(cell_class)
|
126
|
-
setup_test_states_in(subject) # add #in_view to subject cell.
|
127
|
-
subject.render_state(:in_view, block)
|
128
|
-
end
|
129
|
-
|
130
|
-
protected
|
131
|
-
def setup_test_states_in(cell)
|
132
|
-
cell.instance_eval do
|
133
|
-
def in_view(block=nil)
|
134
|
-
render :inline => "<%= instance_exec(&block) %>", :locals => {:block => block}
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
include TestMethods
|
141
|
-
# imports "their" #assert_select.
|
142
|
-
if ::Rails.const_defined?(:Dom)
|
143
|
-
include ::Rails::Dom::Testing::Assertions::SelectorAssertions
|
144
|
-
else
|
145
|
-
include ActionDispatch::Assertions::SelectorAssertions
|
146
|
-
end
|
147
|
-
alias_method :rails_assert_select, :assert_select # i hate that.
|
148
|
-
include AssertSelect
|
149
|
-
|
150
|
-
extend ActionController::TestCase::Behavior::ClassMethods
|
151
|
-
class_attribute :_controller_class
|
152
|
-
|
153
|
-
|
154
|
-
def invoke(state, *args)
|
155
|
-
@last_invoke = self.class.controller_class.new(@controller).render_state(state, *args)
|
156
|
-
end
|
157
|
-
|
158
|
-
if Cell.rails_version.~("4.0", "4.1")
|
159
|
-
include ActiveSupport::Testing::ConstantLookup
|
160
|
-
def self.determine_default_controller_class(name) # FIXME: fix that in Rails 4.x.
|
161
|
-
determine_constant_from_test_name(name) do |constant|
|
162
|
-
Class === constant #&& constant < ActionController::Metal
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
8
|
+
include Testing
|
167
9
|
end
|
168
10
|
end
|
data/lib/cell/testing.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Used in rspec-cells, etc.
|
2
|
+
module Cell
|
3
|
+
module Testing
|
4
|
+
def cell(name, *args)
|
5
|
+
ViewModel.cell_for(name, controller, *args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def concept(name, *args)
|
9
|
+
Concept.cell_for(name, controller, *args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def controller
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/cell/twin.rb
CHANGED
@@ -1,38 +1,20 @@
|
|
1
1
|
require 'disposable/twin'
|
2
|
-
require 'disposable/twin/option'
|
3
|
-
module Cell
|
4
|
-
class Twin < Disposable::Twin
|
5
|
-
include Option
|
6
2
|
|
7
|
-
|
8
|
-
|
3
|
+
module Cell
|
4
|
+
module Twin
|
5
|
+
def self.included(base)
|
6
|
+
base.send :include, Disposable::Twin::Builder
|
7
|
+
base.extend ClassMethods
|
9
8
|
end
|
10
9
|
|
11
|
-
module
|
12
|
-
def
|
13
|
-
|
14
|
-
base.inheritable_attr :twin_class
|
15
|
-
base.extend ClassMethods
|
16
|
-
end
|
17
|
-
|
18
|
-
module ClassMethods
|
19
|
-
def properties(twin_class)
|
20
|
-
twin_class.property_names.each { |name| property name }
|
21
|
-
self.twin_class = twin_class
|
22
|
-
end
|
23
|
-
|
24
|
-
alias_method :twin, :properties
|
10
|
+
module ClassMethods
|
11
|
+
def twin(twin_class)
|
12
|
+
super(twin_class) { |dfn| property dfn.name } # create readers to twin model.
|
25
13
|
end
|
14
|
+
end
|
26
15
|
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def build_twin(*args)
|
34
|
-
self.class.twin_class.new(*args)
|
35
|
-
end
|
16
|
+
def initialize(controller, model, options={})
|
17
|
+
super(controller, build_twin(model, options))
|
36
18
|
end
|
37
19
|
end
|
38
20
|
end
|
data/lib/cell/version.rb
ADDED
data/lib/cell/view_model.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# FIXME: remove AC dependency by delegating forgery
|
2
|
+
require 'action_controller'
|
3
|
+
|
1
4
|
# no helper_method calls
|
2
5
|
# no instance variables
|
3
6
|
# no locals
|
@@ -5,121 +8,226 @@
|
|
5
8
|
# call "helpers" in class
|
6
9
|
|
7
10
|
# TODO: warn when using ::property but not passing in model in constructor.
|
8
|
-
|
11
|
+
module Cell
|
12
|
+
class ViewModel < AbstractController::Base
|
13
|
+
abstract!
|
14
|
+
|
15
|
+
extend Uber::InheritableAttr
|
16
|
+
extend Uber::Delegates
|
17
|
+
|
18
|
+
inheritable_attr :view_paths
|
19
|
+
self.view_paths = ["app/cells"] # DISCUSS: provide same API as rails?
|
20
|
+
|
21
|
+
inheritable_attr :template_engine
|
22
|
+
self.template_engine = "erb"
|
9
23
|
|
10
|
-
|
11
|
-
|
12
|
-
|
24
|
+
class << self
|
25
|
+
def templates
|
26
|
+
@templates ||= Templates.new # note: this is shared in subclasses. do we really want this?
|
27
|
+
end
|
28
|
+
end
|
13
29
|
|
14
|
-
extend Uber::Delegates
|
15
30
|
|
16
|
-
|
17
|
-
|
18
|
-
include ActionView::Context # this includes CompiledTemplates, too.
|
19
|
-
# properties :title, :body
|
20
|
-
attr_reader :model
|
31
|
+
include Prefixes
|
32
|
+
extend SelfContained
|
21
33
|
|
34
|
+
include Uber::Builder
|
22
35
|
|
23
36
|
|
24
|
-
|
25
|
-
|
26
|
-
def collection(name, controller, array, method=:show)
|
27
|
-
# FIXME: this is the problem in Concept cells, we don't wanna call Cell::Rails.cell_for here.
|
28
|
-
array.collect { |model| cell_for(name, controller, model).call(method) }.join("\n").html_safe
|
37
|
+
def self.controller_path
|
38
|
+
@controller_path ||= name.sub(/Cell$/, '').underscore
|
29
39
|
end
|
30
40
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
41
|
+
include ActionController::RequestForgeryProtection
|
42
|
+
delegate :session, :params, :request, :config, :env, :url_options, :to => :parent_controller
|
43
|
+
|
44
|
+
attr_reader :model
|
45
|
+
|
46
|
+
|
47
|
+
module Helpers
|
48
|
+
# Renders collection of cells.
|
49
|
+
def cells_collection(name, controller, array, options)
|
50
|
+
method = options.delete(:method) || :show
|
51
|
+
join = options.delete(:collection_join)
|
52
|
+
array.collect { |model| cell_for(name, *[controller, model, options]).call(method) }.join(join).html_safe
|
35
53
|
end
|
36
54
|
|
37
|
-
|
55
|
+
# Returns cell instance.
|
56
|
+
def cell(name, controller, model=nil, options={}, &block) # classic Rails fuzzy API.
|
57
|
+
if model.is_a?(Hash) and array = model.delete(:collection)
|
58
|
+
return cells_collection(name, controller, array, model)
|
59
|
+
end
|
60
|
+
|
61
|
+
cell_for(name, controller, model, options, &block)
|
62
|
+
end
|
38
63
|
end
|
39
|
-
|
40
|
-
|
64
|
+
extend Helpers
|
65
|
+
|
66
|
+
|
67
|
+
class << self
|
68
|
+
def property(*names)
|
69
|
+
delegates :model, *names # Uber::Delegates.
|
70
|
+
end
|
71
|
+
|
72
|
+
include Helpers
|
41
73
|
|
42
74
|
|
43
|
-
|
44
|
-
|
45
|
-
|
75
|
+
def cell_for(name, controller, *args)
|
76
|
+
class_from_cell_name(name).build_cell(controller, *args)
|
77
|
+
end
|
78
|
+
|
79
|
+
def class_from_cell_name(name)
|
80
|
+
"#{name}_cell".classify.constantize
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_cell(controller, *args)
|
84
|
+
class_builder.call(*args).new(controller, *args) # Uber::Builder::class_builder.
|
85
|
+
end
|
46
86
|
end
|
47
87
|
|
48
|
-
|
49
|
-
|
88
|
+
def cell(name, *args)
|
89
|
+
self.class.cell(name, parent_controller, *args)
|
90
|
+
end
|
50
91
|
|
51
|
-
def cell(name, *args)
|
52
|
-
self.class.cell(name, parent_controller, *args)
|
53
|
-
end
|
54
92
|
|
93
|
+
def initialize(controller, model=nil, options={})
|
94
|
+
@parent_controller = controller # TODO: this is removed in 4.0.
|
55
95
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
96
|
+
setup!(model, options)
|
97
|
+
end
|
98
|
+
attr_reader :parent_controller
|
99
|
+
alias_method :controller, :parent_controller
|
60
100
|
|
61
|
-
# render :show
|
62
|
-
def render(options={})
|
63
|
-
options = options_for(options, caller) # TODO: call render methods with call(:show), call(:comments) instead of directly #comments?
|
64
101
|
|
65
|
-
|
66
|
-
|
102
|
+
# render :show
|
103
|
+
def render(options={})
|
104
|
+
options = normalize_options(options, caller) # TODO: call render methods with call(:show), call(:comments) instead of directly #comments?
|
105
|
+
render_to_string(options)
|
106
|
+
end
|
67
107
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
content.to_s.html_safe
|
76
|
-
end
|
108
|
+
def render_to_string(options)
|
109
|
+
template = template_for(options) # TODO: cache template with path/lookup keys.
|
110
|
+
content = template.render(self, options[:locals])
|
111
|
+
|
112
|
+
# TODO: allow other (global) layout dirs.
|
113
|
+
with_layout(options, content)
|
114
|
+
end
|
77
115
|
|
78
|
-
private
|
79
|
-
def view_context
|
80
|
-
self
|
81
|
-
end
|
82
116
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
117
|
+
# Invokes the passed method (defaults to :show). This will respect caching and marks the string as html_safe.
|
118
|
+
#
|
119
|
+
# Please use #call instead of calling methods directly. This allows adding caching later without changing
|
120
|
+
# your code.
|
121
|
+
#
|
122
|
+
# Yields +self+ (the cell instance) to an optional block.
|
123
|
+
def call(state=:show, *args)
|
124
|
+
content = render_state(state, *args)
|
125
|
+
yield self if block_given?
|
126
|
+
|
127
|
+
content.to_s.html_safe
|
88
128
|
end
|
89
|
-
end
|
90
129
|
|
91
|
-
|
92
|
-
|
93
|
-
|
130
|
+
alias_method :to_s, :call
|
131
|
+
|
132
|
+
private
|
133
|
+
attr_reader :options
|
134
|
+
|
135
|
+
def setup!(model, options)
|
136
|
+
@model = model
|
137
|
+
@options = options
|
138
|
+
# or: create_twin(model, options)
|
139
|
+
end
|
140
|
+
|
141
|
+
module Rendering
|
142
|
+
def render_state(*args)
|
143
|
+
send(*args)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
include Rendering
|
147
|
+
include Caching
|
148
|
+
|
149
|
+
def output_buffer
|
150
|
+
@output_buffer ||= []
|
151
|
+
end
|
152
|
+
attr_writer :output_buffer # TODO: test that, this breaks in MM.
|
153
|
+
|
154
|
+
module TemplateFor
|
155
|
+
def template_for(options)
|
156
|
+
view = options[:view]
|
157
|
+
engine = options[:template_engine]
|
158
|
+
base = options[:base]
|
159
|
+
prefixes = options[:prefixes]
|
160
|
+
|
161
|
+
# we could also pass _prefixes when creating class.templates, because prefixes are never gonna change per instance. not too sure if i'm just assuming this or if people need that.
|
162
|
+
self.class.templates[base, prefixes, view, engine] or raise TemplateMissingError.new(base, prefixes, view, engine, nil)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
include TemplateFor
|
166
|
+
|
167
|
+
def with_layout(options, content)
|
168
|
+
return content unless layout = options[:layout]
|
94
169
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
# FIXME: fix that in rails core.
|
103
|
-
if Cell.rails_version.~("4.0", "4.1")
|
104
|
-
include ActionView::Helpers::UrlHelper # gives us breaking #url_for.
|
105
|
-
|
106
|
-
def url_for(options = nil) # from ActionDispatch:R:UrlFor.
|
107
|
-
case options
|
108
|
-
when nil
|
109
|
-
_routes.url_for(url_options.symbolize_keys)
|
110
|
-
when Hash
|
111
|
-
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
|
112
|
-
when String
|
113
|
-
options
|
114
|
-
when Array
|
115
|
-
polymorphic_url(options, options.extract_options!)
|
170
|
+
template = template_for(options.merge :view => layout) # we could also allow a different layout engine, etc.
|
171
|
+
template.render(self) { content }
|
172
|
+
end
|
173
|
+
|
174
|
+
def normalize_options(options, caller) # TODO: rename to #setup_options! to be inline with Trb.
|
175
|
+
options = if options.is_a?(Hash)
|
176
|
+
options.reverse_merge(:view => state_for_implicit_render(caller)) # TODO: test implicit render!
|
116
177
|
else
|
117
|
-
|
178
|
+
{:view => options.to_s}
|
179
|
+
end
|
180
|
+
|
181
|
+
options[:template_engine] ||= self.class.template_engine # DISCUSS: in separate method?
|
182
|
+
options[:base] ||= self.class.view_paths
|
183
|
+
options[:prefixes] ||= _prefixes
|
184
|
+
|
185
|
+
process_options!(options)
|
186
|
+
options
|
187
|
+
end
|
188
|
+
|
189
|
+
# Overwrite #process_options in included feature modules, but don't forget to call +super+.
|
190
|
+
module ProcessOptions
|
191
|
+
def process_options!(options)
|
118
192
|
end
|
119
193
|
end
|
120
|
-
|
121
|
-
else
|
122
|
-
include ActionView::Helpers::UrlHelper
|
123
|
-
end
|
194
|
+
include ProcessOptions
|
124
195
|
|
125
|
-
|
196
|
+
|
197
|
+
def state_for_implicit_render(caller)
|
198
|
+
caller[0].match(/`(\w+)/)[1]
|
199
|
+
end
|
200
|
+
|
201
|
+
include Layout
|
202
|
+
|
203
|
+
|
204
|
+
if defined?(ActionView)
|
205
|
+
# 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.
|
206
|
+
# if we could mix in everything else from the helper except for the #url_for, it would be fine.
|
207
|
+
# FIXME: fix that in rails core.
|
208
|
+
if Cell.rails_version <= Gem::Version.new('4.0')
|
209
|
+
include ActionView::Helpers::UrlHelper # gives us breaking #url_for.
|
210
|
+
|
211
|
+
def url_for(options = nil) # from ActionDispatch:R:UrlFor.
|
212
|
+
case options
|
213
|
+
when nil
|
214
|
+
_routes.url_for(url_options.symbolize_keys)
|
215
|
+
when Hash
|
216
|
+
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
|
217
|
+
when String
|
218
|
+
options
|
219
|
+
when Array
|
220
|
+
polymorphic_url(options, options.extract_options!)
|
221
|
+
else
|
222
|
+
polymorphic_url(options)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
public :url_for
|
227
|
+
else
|
228
|
+
include ActionView::Helpers::UrlHelper
|
229
|
+
end
|
230
|
+
include ActionView::Helpers::FormTagHelper
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|