cells 3.11.3 → 4.0.0.beta1
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.
- 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
|