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.
Files changed (204) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +15 -13
  4. data/Appraisals +23 -0
  5. data/CHANGES.md +20 -3
  6. data/Gemfile +5 -7
  7. data/README.md +317 -236
  8. data/Rakefile +1 -1
  9. data/TODO.md +3 -0
  10. data/cells.gemspec +19 -28
  11. data/gemfiles/rails3.2.gemfile +14 -0
  12. data/gemfiles/rails4.0.gemfile +14 -0
  13. data/gemfiles/rails4.1.gemfile +15 -0
  14. data/gemfiles/rails4.2.gemfile +14 -0
  15. data/lib/cell.rb +28 -21
  16. data/lib/cell/caching.rb +10 -22
  17. data/lib/cell/caching/notification.rb +15 -0
  18. data/lib/cell/concept.rb +4 -69
  19. data/lib/cell/development.rb +11 -0
  20. data/lib/{cells → cell}/engines.rb +1 -1
  21. data/lib/cell/layout.rb +20 -0
  22. data/lib/cell/partial.rb +17 -0
  23. data/lib/cell/{base/prefixes.rb → prefixes.rb} +1 -1
  24. data/lib/cell/rails.rb +58 -50
  25. data/lib/cell/railtie.rb +60 -0
  26. data/lib/cell/{base/self_contained.rb → self_contained.rb} +1 -1
  27. data/lib/cell/templates.rb +60 -0
  28. data/lib/cell/test_case.rb +4 -162
  29. data/lib/cell/testing.rb +15 -0
  30. data/lib/cell/twin.rb +11 -29
  31. data/lib/cell/version.rb +10 -0
  32. data/lib/cell/view_model.rb +196 -88
  33. data/lib/cells.rb +1 -20
  34. data/lib/rails/generators/cell/cell_generator.rb +43 -0
  35. data/lib/rails/generators/cell/templates/cell.rb.erb +8 -0
  36. data/lib/{generators/templates/concept → rails/generators/cell/templates}/view.erb +0 -0
  37. data/lib/{generators/templates/concept → rails/generators/cell/templates}/view.haml +0 -0
  38. data/lib/rails/generators/cell/templates/view.slim +2 -0
  39. data/lib/rails/generators/concept/concept_generator.rb +38 -0
  40. data/lib/{generators/templates/concept/cell.rb → rails/generators/concept/templates/concept.rb.erb} +2 -2
  41. data/lib/{generators → rails/generators/concept}/templates/view.erb +0 -0
  42. data/lib/{generators → rails/generators/concept}/templates/view.haml +0 -0
  43. data/lib/rails/generators/concept/templates/view.slim +2 -0
  44. data/lib/rails/generators/test_unit/cell/cell_generator.rb +21 -0
  45. data/lib/{generators/templates/cell_test.rb → rails/generators/test_unit/cell/templates/unit_test.rb.erb} +3 -3
  46. data/lib/rails/generators/test_unit/concept/concept_generator.rb +21 -0
  47. data/lib/rails/generators/test_unit/concept/templates/unit_test.rb.erb +11 -0
  48. data/lib/{cells → tasks}/cells.rake +0 -0
  49. data/test/builder_test.rb +58 -0
  50. data/test/caching_test.rb +298 -0
  51. data/test/cell_benchmark.rb +32 -0
  52. data/test/cell_generator_test.rb +51 -82
  53. data/test/cell_test.rb +8 -23
  54. data/test/concept_generator_test.rb +22 -13
  55. data/test/concept_test.rb +41 -75
  56. data/test/dummy/app/views/musician/hamlet.html.erb +1 -0
  57. data/test/dummy/config/application.rb +21 -8
  58. data/test/{app/cells/bassist/play.html.erb → fixtures/bassist/play.erb} +0 -0
  59. data/test/fixtures/concepts/record/views/layout.erb +1 -0
  60. data/test/{app → fixtures}/concepts/record/views/show.erb +0 -0
  61. data/test/{app → fixtures}/concepts/record/views/song.erb +0 -0
  62. data/test/fixtures/inherit_views_test/popper/tap.erb +1 -0
  63. data/test/fixtures/inherit_views_test/tapper/play.erb +1 -0
  64. data/test/fixtures/inherit_views_test/tapper/tap.erb +1 -0
  65. data/test/fixtures/partial_test/with_partial/show.erb +1 -0
  66. data/test/fixtures/partials/_show.html.erb +1 -0
  67. data/test/fixtures/partials/_show.xml.erb +1 -0
  68. data/test/fixtures/song/ivar.erb +1 -0
  69. data/test/fixtures/song/show.erb +1 -0
  70. data/test/fixtures/song/with_erb.erb +4 -0
  71. data/test/fixtures/song/with_html.erb +1 -0
  72. data/test/fixtures/song/with_locals.erb +2 -0
  73. data/test/fixtures/song_with_layout/happy.erb +1 -0
  74. data/test/fixtures/song_with_layout/merry.erb +1 -0
  75. data/test/fixtures/song_with_layout/show.erb +1 -0
  76. data/test/fixtures/song_with_layout/show_with_layout.erb +1 -0
  77. data/test/fixtures/templates_caching_test/song/show.erb +1 -0
  78. data/test/fixtures/url_helper_test/song/edit.erb +8 -0
  79. data/test/fixtures/url_helper_test/song/with_block.erb +2 -0
  80. data/test/fixtures/url_helper_test/song/with_capture.erb +4 -0
  81. data/test/fixtures/url_helper_test/song/with_content_tag.erb +6 -0
  82. data/test/fixtures/url_helper_test/song/with_form_for_block.erb +3 -0
  83. data/test/fixtures/url_helper_test/song/with_link_to.erb +3 -0
  84. data/test/layout_test.rb +57 -0
  85. data/test/partial_test.rb +27 -0
  86. data/test/prefixes_test.rb +36 -10
  87. data/test/public_test.rb +42 -0
  88. data/test/rails_extensions_test.rb +51 -0
  89. data/test/render_test.rb +103 -0
  90. data/test/templates_test.rb +45 -0
  91. data/test/test_case_test.rb +21 -122
  92. data/test/test_helper.rb +37 -33
  93. data/test/twin_test.rb +3 -7
  94. data/test/url_helper_test.rb +89 -0
  95. metadata +92 -357
  96. data/gemfiles/Gemfile.rails3-0 +0 -7
  97. data/gemfiles/Gemfile.rails3-1 +0 -7
  98. data/gemfiles/Gemfile.rails3-2 +0 -7
  99. data/gemfiles/Gemfile.rails4-0 +0 -12
  100. data/gemfiles/Gemfile.rails4-1 +0 -12
  101. data/lib/cell/base.rb +0 -82
  102. data/lib/cell/base/view.rb +0 -15
  103. data/lib/cell/builder.rb +0 -71
  104. data/lib/cell/deprecations.rb +0 -41
  105. data/lib/cell/dsl.rb +0 -7
  106. data/lib/cell/rack.rb +0 -32
  107. data/lib/cell/rails/helper_api.rb +0 -37
  108. data/lib/cell/rails/view_model.rb +0 -159
  109. data/lib/cell/rails3_0_strategy.rb +0 -82
  110. data/lib/cell/rails3_1_strategy.rb +0 -40
  111. data/lib/cell/rails4_0_strategy.rb +0 -39
  112. data/lib/cell/rails4_1_strategy.rb +0 -40
  113. data/lib/cell/rendering.rb +0 -109
  114. data/lib/cells/rails.rb +0 -86
  115. data/lib/cells/railtie.rb +0 -38
  116. data/lib/cells/version.rb +0 -3
  117. data/lib/generators/USAGE +0 -30
  118. data/lib/generators/cells/base.rb +0 -22
  119. data/lib/generators/cells/cell_generator.rb +0 -15
  120. data/lib/generators/cells/view_generator.rb +0 -18
  121. data/lib/generators/erb/cell_generator.rb +0 -15
  122. data/lib/generators/erb/concept_generator.rb +0 -17
  123. data/lib/generators/haml/cell_generator.rb +0 -17
  124. data/lib/generators/haml/concept_generator.rb +0 -17
  125. data/lib/generators/rails/cell_generator.rb +0 -16
  126. data/lib/generators/rails/concept_generator.rb +0 -16
  127. data/lib/generators/slim/cell_generator.rb +0 -17
  128. data/lib/generators/templates/cell.rb +0 -9
  129. data/lib/generators/templates/view.slim +0 -4
  130. data/lib/generators/test_unit/cell_generator.rb +0 -14
  131. data/lib/generators/trailblazer/base.rb +0 -21
  132. data/lib/generators/trailblazer/view_generator.rb +0 -18
  133. data/test/app/cells/album/views/cover.haml +0 -1
  134. data/test/app/cells/bad_guitarist/_dii.html.erb +0 -1
  135. data/test/app/cells/bad_guitarist_cell.rb +0 -2
  136. data/test/app/cells/bassist/_dii.html.erb +0 -1
  137. data/test/app/cells/bassist/ahem.html.erb +0 -1
  138. data/test/app/cells/bassist/compose.html.erb +0 -1
  139. data/test/app/cells/bassist/contact_form.html.erb +0 -1
  140. data/test/app/cells/bassist/form_for.erb +0 -3
  141. data/test/app/cells/bassist/form_for_in_haml.haml +0 -2
  142. data/test/app/cells/bassist/jam.html.erb +0 -3
  143. data/test/app/cells/bassist/play.js.erb +0 -1
  144. data/test/app/cells/bassist/pose.html.erb +0 -1
  145. data/test/app/cells/bassist/promote.html.erb +0 -1
  146. data/test/app/cells/bassist/provoke.html.erb +0 -1
  147. data/test/app/cells/bassist/shout.html.erb +0 -1
  148. data/test/app/cells/bassist/sing.html.haml +0 -1
  149. data/test/app/cells/bassist/slap.html.erb +0 -1
  150. data/test/app/cells/bassist/yell.en.html.erb +0 -1
  151. data/test/app/cells/bassist_cell.rb +0 -25
  152. data/test/app/cells/club_security.rb +0 -2
  153. data/test/app/cells/club_security/guard/help.html.erb +0 -1
  154. data/test/app/cells/club_security/guard_cell.rb +0 -6
  155. data/test/app/cells/club_security/medic/help.html.erb +0 -1
  156. data/test/app/cells/club_security/medic_cell.rb +0 -8
  157. data/test/app/cells/layouts/b.erb +0 -1
  158. data/test/app/cells/layouts/metal.html.erb +0 -1
  159. data/test/app/cells/rails_helper_api_test/bassist/edit.html.erb +0 -5
  160. data/test/app/cells/shouter/sing.html.erb +0 -1
  161. data/test/app/cells/song/dashboard.haml +0 -7
  162. data/test/app/cells/song/details.html.haml +0 -1
  163. data/test/app/cells/song/info.html.haml +0 -1
  164. data/test/app/cells/song/lyrics.html.haml +0 -6
  165. data/test/app/cells/song/plays.haml +0 -1
  166. data/test/app/cells/song/scale.haml +0 -1
  167. data/test/app/cells/song/show.html.haml +0 -3
  168. data/test/app/cells/song/title.html.haml +0 -1
  169. data/test/app/cells/trumpeter/promote.html.erb +0 -1
  170. data/test/app/cells/trumpeter_cell.rb +0 -8
  171. data/test/app/cells/view_model_test/comments/show.haml +0 -7
  172. data/test/app/concepts/record/views/layout.haml +0 -2
  173. data/test/app/views/shared/_dong.html.erb +0 -1
  174. data/test/cell_module_test.rb +0 -170
  175. data/test/cells_module_test.rb +0 -27
  176. data/test/deprecations_test.rb +0 -101
  177. data/test/dummy/app/helpers/application_helper.rb +0 -2
  178. data/test/dummy/app/views/musician/hamlet.html.haml +0 -1
  179. data/test/dummy/config/environments/development.rb +0 -16
  180. data/test/dummy/config/environments/production.rb +0 -46
  181. data/test/dummy/config/environments/test.rb +0 -33
  182. data/test/dummy/db/test.sqlite3 +0 -0
  183. data/test/dummy/label/app/cells/label/show.erb +0 -1
  184. data/test/dummy/label/app/cells/label_cell.rb +0 -5
  185. data/test/dummy/label/label.gemspec +0 -20
  186. data/test/dummy/label/lib/label.rb +0 -4
  187. data/test/dummy/label/lib/label/version.rb +0 -3
  188. data/test/dummy/public/404.html +0 -26
  189. data/test/dummy/public/422.html +0 -26
  190. data/test/dummy/public/500.html +0 -26
  191. data/test/dummy/public/favicon.ico +0 -0
  192. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  193. data/test/helper_test.rb +0 -81
  194. data/test/rack_test.rb +0 -32
  195. data/test/rails/asset_pipeline_test.rb +0 -20
  196. data/test/rails/caching_test.rb +0 -456
  197. data/test/rails/cells_test.rb +0 -119
  198. data/test/rails/forms_test.rb +0 -75
  199. data/test/rails/integration_test.rb +0 -299
  200. data/test/rails/render_test.rb +0 -189
  201. data/test/rails/view_model_test.rb +0 -226
  202. data/test/rails/view_test.rb +0 -49
  203. data/test/rails_helper_api_test.rb +0 -58
  204. data/test/self_contained_test.rb +0 -31
@@ -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
@@ -1,6 +1,6 @@
1
1
  # Enforces the new trailblazer directory layout where cells (or concepts in general) are
2
2
  # fully self-contained in its own directory.
3
- module Cell::Base::SelfContained
3
+ module Cell::SelfContained
4
4
  def self_contained!
5
5
  extend Prefixes
6
6
  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
@@ -1,168 +1,10 @@
1
+ require "cell/testing"
2
+
1
3
  module Cell
2
4
  # Test your cells.
3
5
  #
4
- # This class is roughly equal to ActionController::TestCase, exposing the same semantics. It will try
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
- module AssertSelect
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
@@ -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
@@ -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
- def self.property_names
8
- representer_class.representable_attrs.collect(&:name)
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 Properties
12
- def self.included(base)
13
- base.extend Uber::InheritableAttr
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
- def initialize(controller, model, options={})
28
- super(controller, build_twin(model, options))
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
@@ -0,0 +1,10 @@
1
+ module Cell
2
+ module VERSION
3
+ MAJOR = 4
4
+ MINOR = 0
5
+ TINY = 0
6
+ PRE = "beta1"
7
+
8
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
9
+ end
10
+ end
@@ -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
- require 'uber/delegates'
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
- # ViewModel is only supported in Rails +3.1. If you need it in Rails 3.0, let me know.
11
- class Cell::ViewModel < Cell::Rails
12
- abstract!
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
- include Cell::OptionsConstructor
17
- #include ActionView::Helpers::UrlHelper
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
- module Helpers
25
- # DISCUSS: highest level API method. add #cell here.
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
- # TODO: this should be in Helper or something. this should be the only entry point from controller/view.
32
- def cell(name, controller, *args, &block) # classic Rails fuzzy API.
33
- if args.first.is_a?(Hash) and array = args.first[:collection]
34
- return collection(name, controller, array)
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
- cell_for(name, controller, *args, &block)
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
- end
40
- extend Helpers # FIXME: do we really need ViewModel::cell/::collection ?
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
- class << self
44
- def property(*names)
45
- delegates :model, *names # Uber::Delegates.
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
- include Helpers
49
- end
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
- def initialize(*args)
57
- super
58
- _prepare_context # happens in AV::Base at the bottom.
59
- end
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
- super
66
- end
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
- # Invokes the passed state (defaults to :show) by using +render_state+. This will respect caching.
69
- # Yields +self+ (the cell instance) to an optional block.
70
- def call(state=:show)
71
- # it is ok to call to_s.html_safe here as #call is a defined rendering method.
72
- # DISCUSS: IN CONCEPT: render( view: implicit_state)
73
- content = render_state(state)
74
- yield self if block_given?
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
- def options_for(options, caller)
84
- if options.is_a?(Hash)
85
- options.reverse_merge(:view => state_for_implicit_render(caller)) # TODO: test implicit render!
86
- else
87
- {:view => options.to_s}
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
- def state_for_implicit_render(caller)
92
- caller[0].match(/`(\w+)/)[1]
93
- end
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
- # def implicit_state
96
- # controller_path.split("/").last
97
- # end
98
-
99
-
100
- # 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.
101
- # if we could mix in everything else from the helper except for the #url_for, it would be fine.
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
- polymorphic_url(options)
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
- public :url_for
121
- else
122
- include ActionView::Helpers::UrlHelper
123
- end
194
+ include ProcessOptions
124
195
 
125
- end
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