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.
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