cells 3.10.1 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. data/.travis.yml +7 -0
  2. data/CHANGES.md +18 -0
  3. data/Gemfile +1 -1
  4. data/README.md +182 -57
  5. data/TODO.md +9 -0
  6. data/cells.gemspec +3 -1
  7. data/gemfiles/Gemfile.rails4-0 +2 -1
  8. data/gemfiles/Gemfile.rails4-1 +5 -3
  9. data/lib/cell/base.rb +33 -64
  10. data/lib/cell/base/prefixes.rb +27 -0
  11. data/lib/cell/base/self_contained.rb +13 -0
  12. data/lib/cell/base/view.rb +15 -0
  13. data/lib/cell/builder.rb +52 -53
  14. data/lib/cell/caching.rb +21 -4
  15. data/lib/cell/concept.rb +85 -0
  16. data/lib/cell/rails.rb +13 -6
  17. data/lib/cell/rails/view_model.rb +47 -6
  18. data/lib/cell/rendering.rb +13 -13
  19. data/lib/cell/test_case.rb +3 -3
  20. data/lib/cell/view_model.rb +151 -0
  21. data/lib/cells.rb +0 -60
  22. data/lib/cells/rails.rb +10 -5
  23. data/lib/cells/railtie.rb +2 -5
  24. data/lib/cells/version.rb +1 -1
  25. data/lib/generators/erb/concept_generator.rb +17 -0
  26. data/lib/generators/haml/concept_generator.rb +17 -0
  27. data/lib/generators/rails/concept_generator.rb +16 -0
  28. data/lib/generators/templates/concept/cell.rb +9 -0
  29. data/lib/generators/templates/concept/view.erb +7 -0
  30. data/lib/generators/templates/concept/view.haml +4 -0
  31. data/lib/generators/trailblazer/base.rb +21 -0
  32. data/lib/generators/trailblazer/view_generator.rb +18 -0
  33. data/test/app/cells/bassist_cell.rb +1 -1
  34. data/test/app/cells/record/views/layout.haml +2 -0
  35. data/test/app/cells/record/views/show.erb +1 -0
  36. data/test/app/cells/record/views/song.erb +1 -0
  37. data/test/app/cells/song/scale.haml +1 -0
  38. data/test/cell_module_test.rb +18 -37
  39. data/test/cells_module_test.rb +1 -1
  40. data/test/concept_generator_test.rb +26 -0
  41. data/test/concept_test.rb +78 -0
  42. data/test/dummy/Rakefile +1 -1
  43. data/test/dummy/app/assets/javascripts/application.js +2 -0
  44. data/test/dummy/app/cells/album/assets/album.js +1 -0
  45. data/test/dummy/app/concepts/song/assets/songs.js +1 -0
  46. data/test/dummy/config/application.rb +11 -6
  47. data/test/dummy/label/label.gemspec +2 -2
  48. data/test/helper_test.rb +2 -2
  49. data/test/prefixes_test.rb +75 -0
  50. data/test/rails/asset_pipeline_test.rb +20 -0
  51. data/test/rails/caching_test.rb +1 -9
  52. data/test/rails/cells_test.rb +2 -17
  53. data/test/rails/forms_test.rb +2 -1
  54. data/test/rails/integration_test.rb +199 -8
  55. data/test/rails/render_test.rb +2 -10
  56. data/test/rails/view_model_test.rb +135 -60
  57. data/test/rails_helper_api_test.rb +2 -1
  58. data/test/self_contained_test.rb +9 -2
  59. data/test/test_case_test.rb +2 -2
  60. data/test/test_helper.rb +2 -3
  61. metadata +117 -33
  62. checksums.yaml +0 -7
  63. data/test/dummy/app/controllers/musician_controller.rb +0 -36
  64. data/test/rails/router_test.rb +0 -45
@@ -96,7 +96,7 @@ module Cell
96
96
  # assert_select html, "h1", "The latest and greatest!"
97
97
  def render_cell(name, state, *args)
98
98
  # DISCUSS: should we allow passing a block here, just as in controllers?
99
- @subject_cell = ::Cell::Rails.create_cell_for(name, @controller, *args)
99
+ @subject_cell = ::Cell::Rails.cell_for(name, @controller, *args)
100
100
  @view_assigns = extract_state_ivars_for(@subject_cell) do
101
101
  @last_invoke = @subject_cell.render_state(state, *args)
102
102
  end
@@ -110,7 +110,7 @@ module Cell
110
110
  # Example:
111
111
  # assert_equal "Doo Dumm Dumm..." cell(:bassist).play
112
112
  def cell(name, *args, &block)
113
- Cell::Rails.create_cell_for(name, @controller, *args).tap do |cell|
113
+ Cell::Rails.cell_for(name, @controller, *args).tap do |cell|
114
114
  cell.instance_eval &block if block_given?
115
115
  end
116
116
  end
@@ -150,7 +150,7 @@ module Cell
150
150
  @last_invoke = self.class.controller_class.new(@controller).render_state(state, *args)
151
151
  end
152
152
 
153
- if Cell.rails4_0? or Cell.rails4_1?
153
+ if Cell.rails_version.~("4.0", "4.1")
154
154
  include ActiveSupport::Testing::ConstantLookup
155
155
  def self.determine_default_controller_class(name) # FIXME: fix that in Rails 4.x.
156
156
  determine_constant_from_test_name(name) do |constant|
@@ -0,0 +1,151 @@
1
+ # no helper_method calls
2
+ # no instance variables
3
+ # no locals
4
+ # options are automatically made instance methods via constructor.
5
+ # call "helpers" in class
6
+
7
+ # TODO: warn when using ::property but not passing in model in constructor.
8
+
9
+ class Cell::ViewModel < Cell::Rails
10
+ abstract!
11
+
12
+ include Cell::OptionsConstructor
13
+ #include ActionView::Helpers::UrlHelper
14
+ include ActionView::Context # this includes CompiledTemplates, too.
15
+ # properties :title, :body
16
+ attr_reader :model
17
+
18
+
19
+
20
+ module Helpers
21
+ # DISCUSS: highest level API method. add #cell here.
22
+ def collection(name, controller, array, method=:show)
23
+ # FIXME: this is the problem in Concept cells, we don't wanna call Cell::Rails.cell_for here.
24
+ array.collect { |model| cell_for(name, controller, model).call(method) }.join("\n").html_safe
25
+ end
26
+
27
+ # TODO: this should be in Helper or something. this should be the only entry point from controller/view.
28
+ def cell(name, controller, *args, &block) # classic Rails fuzzy API.
29
+ if args.first.is_a?(Hash) and array = args.first[:collection]
30
+ return collection(name, controller, array)
31
+ end
32
+
33
+ cell_for(name, controller, *args, &block)
34
+ end
35
+ end
36
+ extend Helpers # FIXME: do we really need ViewModel::cell/::collection ?
37
+
38
+
39
+ class << self
40
+ def property(*names)
41
+ delegate *names, :to => :model
42
+ end
43
+
44
+ include Helpers
45
+ end
46
+
47
+ def cell(name, *args)
48
+ self.class.cell(name, parent_controller, *args)
49
+ end
50
+
51
+
52
+ def initialize(*args)
53
+ super
54
+ _prepare_context # happens in AV::Base at the bottom.
55
+ end
56
+
57
+ def render(options={})
58
+ if options.is_a?(Hash)
59
+ options.reverse_merge!(:view => state_for_implicit_render)
60
+ else
61
+ options = {:view => options.to_s}
62
+ end
63
+
64
+ super
65
+ end
66
+
67
+ def call(state=:show)
68
+ # it is ok to call to_s.html_safe here as #call is a defined rendering method.
69
+ # DISCUSS: IN CONCEPT: render( view: implicit_state)
70
+ render_state(state).to_s.html_safe
71
+ end
72
+
73
+ private
74
+ def view_context
75
+ self
76
+ end
77
+
78
+ def state_for_implicit_render()
79
+ caller[1].match(/`(\w+)/)[1]
80
+ end
81
+
82
+ # def implicit_state
83
+ # controller_path.split("/").last
84
+ # end
85
+
86
+
87
+ # FIXME: this module is to fix a design flaw in Rails 4.0. the problem is that AV::UrlHelper mixes in the wrong #url_for.
88
+ # if we could mix in everything else from the helper except for the #url_for, it would be fine.
89
+ module LinkToHelper
90
+ include ActionView::Helpers::TagHelper
91
+
92
+ def link_to(name = nil, options = nil, html_options = nil, &block)
93
+ html_options, options, name = options, name, block if block_given?
94
+ options ||= {}
95
+
96
+ html_options = convert_options_to_data_attributes(options, html_options)
97
+
98
+ url = url_for(options)
99
+ html_options['href'] ||= url
100
+
101
+ content_tag(:a, name || url, html_options, &block)
102
+ end
103
+
104
+ def convert_options_to_data_attributes(options, html_options)
105
+ if html_options
106
+ html_options = html_options.stringify_keys
107
+ html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options)
108
+
109
+ disable_with = html_options.delete("disable_with")
110
+ confirm = html_options.delete('confirm')
111
+ method = html_options.delete('method')
112
+
113
+ if confirm
114
+ message = ":confirm option is deprecated and will be removed from Rails 4.1. " \
115
+ "Use 'data: { confirm: \'Text\' }' instead."
116
+ ActiveSupport::Deprecation.warn message
117
+
118
+ html_options["data-confirm"] = confirm
119
+ end
120
+
121
+ add_method_to_attributes!(html_options, method) if method
122
+
123
+ if disable_with
124
+ message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \
125
+ "Use 'data: { disable_with: \'Text\' }' instead."
126
+ ActiveSupport::Deprecation.warn message
127
+
128
+ html_options["data-disable-with"] = disable_with
129
+ end
130
+
131
+ html_options
132
+ else
133
+ link_to_remote_options?(options) ? {'data-remote' => 'true'} : {}
134
+ end
135
+ end
136
+
137
+ def link_to_remote_options?(options)
138
+ if options.is_a?(Hash)
139
+ options.delete('remote') || options.delete(:remote)
140
+ end
141
+ end
142
+ end
143
+
144
+ # FIXME: fix that in rails core.
145
+ if Cell.rails_version.~("4.0", "4.1")
146
+ include LinkToHelper
147
+ else
148
+ include ActionView::Helpers::UrlHelper
149
+ end
150
+
151
+ end
@@ -1,63 +1,3 @@
1
- # = Cells
2
- #
3
- # Cells are view components for Rails. Being lightweight controllers with actions and views, cells are the
4
- # answer to <tt>DoubleRenderError</tt>s and the long awaited ability to render actions within actions.
5
- #
6
- # == Directory structure
7
- #
8
- # Cells live in +app/cells/+ and have a similar file layout as controllers.
9
- #
10
- # app/
11
- # cells/
12
- # shopping_cart_cell.rb
13
- # shopping_cart/
14
- # status.html.erb
15
- # product_list.haml
16
- # layouts/
17
- # box.html.erb
18
- #
19
- # == Cell nesting
20
- #
21
- # Is is good practice to split up complex cell views into multiple states or views. Remember, you can always use
22
- # <tt>render :view => ...</tt> and <tt>render :state => ...</tt> in your views.
23
- #
24
- # Following this, you stick to encapsulation and save your code from getting inscrutable, as it happens in most
25
- # controller views, partials, and so called "helpers".
26
- #
27
- # Given the following setup:
28
- #
29
- # class ShoppingCartCell < Cell::Base
30
- # def cart
31
- # @items = items_in_cart
32
- # render
33
- # end
34
- #
35
- # def order_button
36
- # render
37
- # end
38
- #
39
- # You could now render the "Order!" button in the +cart.haml+ view.
40
- #
41
- # - for item in @items
42
- # = @item.title
43
- #
44
- # render :state => :order_button
45
- #
46
- # which is more than just a partial, as you may execute additional code in the state method.
47
- #
48
- # == View inheritance
49
- #
50
- # Unlike controllers, Cells can form a class hierarchy. Even views are inherited, which is pretty useful
51
- # when overriding only small parts of the view.
52
- #
53
- # So if you'd need a special "Order!" button with sparkling stars on christmas, your cell would go like this.
54
- #
55
- # class XmasCartCell < ShoppingCartCell
56
- # end
57
- #
58
- # Beside your new class you'd provide a star-sprangled button view in +xmas_cart/order_button.haml+.
59
- # When rendering the +cart+ state, the states as well as the "missing" views are inherited from ancesting cells,
60
- # this is pretty DRY and object-oriented, isn't it?
61
1
  module Cells
62
2
  # Setup your special needs for Cells here. Use this to add new view paths.
63
3
  #
@@ -4,10 +4,16 @@ module Cells
4
4
  module Rails
5
5
  module ActionController
6
6
  def cell_for(name, *args, &block)
7
+ return Cell::Rails::ViewModel.cell(name, self, *args, &block) if args.first.is_a?(Hash) and args.first[:collection] # FIXME: we only want this feature in view models for now.
7
8
  ::Cell::Base.cell_for(name, self, *args, &block)
8
9
  end
9
10
  alias_method :cell, :cell_for # DISCUSS: make this configurable?
10
11
 
12
+ def concept_for(name, *args, &block)
13
+ return Cell::Concept.cell(name, self, *args, &block)
14
+ end
15
+ alias_method :concept, :concept_for
16
+
11
17
  # Renders the cell state and returns the content. You may pass options here, too. They will be
12
18
  # around in @opts.
13
19
  #
@@ -42,11 +48,6 @@ module Cells
42
48
  #
43
49
  # will expire the view for state <tt>:display_list</tt> in the cell <tt>MyListingCell</tt>.
44
50
  def expire_cell_state(cell_class, state, args={}, opts=nil)
45
- if cell_class.is_a?(Symbol)
46
- ActiveSupport::Deprecation.warn "Please pass the cell class into #expire_cell_state, as in expire_cell_state(DirectorCell, :count, :user_id => 1)"
47
- cell_class = Cell::Rails.class_from_cell_name(cell_class)
48
- end
49
-
50
51
  key = cell_class.state_cache_key(state, args)
51
52
  cell_class.expire_cache_key(key, opts)
52
53
  end
@@ -66,6 +67,10 @@ module Cells
66
67
  def render_cell(name, state, *args, &block)
67
68
  ::Cell::Rails.render_cell_for(name, state, controller, *args, &block)
68
69
  end
70
+
71
+ def concept(name, *args, &block)
72
+ controller.concept_for(name, *args, &block)
73
+ end
69
74
  end
70
75
  end
71
76
  end
@@ -7,14 +7,10 @@ module Cells
7
7
 
8
8
  initializer "cells.attach_router" do |app|
9
9
  Cell::Base.class_eval do
10
- include app.routes.url_helpers
10
+ include app.routes.url_helpers # TODO: i hate this, make it better in Rails.
11
11
  end
12
12
  end
13
13
 
14
- initializer "cells.setup_view_paths" do |app|
15
- Cell::Base.setup_view_paths!
16
- end
17
-
18
14
  initializer "cells.setup_engines_view_paths" do |app|
19
15
  Cells::Engines.append_engines_view_paths_for(app.config.action_controller)
20
16
  end
@@ -24,6 +20,7 @@ module Cells
24
20
  (app.config.cells.with_assets or []).each do |name|
25
21
  # FIXME: this doesn't take engine cells into account.
26
22
  app.config.assets.paths << "#{app.root}/app/cells/#{name}/assets"
23
+ app.config.assets.paths << "#{app.root}/app/concepts/#{name}/assets" # TODO: find out type.
27
24
  end
28
25
  end
29
26
 
@@ -1,3 +1,3 @@
1
1
  module Cells
2
- VERSION = '3.10.1'
2
+ VERSION = '3.11.0'
3
3
  end
@@ -0,0 +1,17 @@
1
+ require 'generators/trailblazer/view_generator'
2
+
3
+ module Erb
4
+ module Generators
5
+ class ConceptGenerator < ::Trailblazer::Generators::ViewGenerator
6
+
7
+ source_root File.expand_path('../../templates/concept', __FILE__)
8
+
9
+ private
10
+ def handler
11
+ :erb
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+
@@ -0,0 +1,17 @@
1
+ require 'generators/trailblazer/view_generator'
2
+
3
+ module Haml
4
+ module Generators
5
+ class ConceptGenerator < ::Trailblazer::Generators::ViewGenerator
6
+
7
+ source_root File.expand_path('../../templates/concept', __FILE__)
8
+
9
+ private
10
+ def handler
11
+ :haml
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+
@@ -0,0 +1,16 @@
1
+ require 'generators/trailblazer/base'
2
+
3
+ module Rails
4
+ module Generators
5
+ class ConceptGenerator < ::Trailblazer::Generators::Cell
6
+ source_root File.expand_path('../../templates/concept', __FILE__)
7
+
8
+ def create_cell_file
9
+ template 'cell.rb', "#{base_path}/cell.rb"
10
+ end
11
+
12
+ hook_for(:template_engine)
13
+ hook_for(:test_framework)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ class <%= class_name %>::Cell < <%= options.base_cell_class %>
2
+ include Concept
3
+
4
+ <% for action in actions -%>
5
+ def <%= action %>
6
+ render
7
+ end
8
+ <% end -%>
9
+ end
@@ -0,0 +1,7 @@
1
+ <h1>
2
+ <%= class_name %>#<%= @state %>
3
+ </h1>
4
+
5
+ <p>
6
+ Find me in <%= @path %>
7
+ </p>
@@ -0,0 +1,4 @@
1
+ %h1
2
+ <%= class_name %>#<%= @state %>
3
+ %p
4
+ Find me in <%= @path %>
@@ -0,0 +1,21 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/named_base'
3
+
4
+ module Trailblazer
5
+ module Generators
6
+ class Cell < ::Rails::Generators::NamedBase
7
+ class_option :template_engine
8
+ class_option :test_framework
9
+ class_option :base_cell_class, :type => :string, :default => "Cell::Rails"
10
+ class_option :base_cell_path
11
+
12
+ argument :actions, :type => :array, :default => [:show], :banner => "action action"
13
+
14
+ private
15
+ def base_path
16
+ path = (options[:base_cell_path] || 'app/concepts').to_s
17
+ File.join(path, class_path, file_name)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ module Trailblazer
2
+ module Generators
3
+ class ViewGenerator < Cell # Trailblazer::Generators::Cell
4
+ def create_views
5
+ for state in actions do
6
+ @state = state
7
+ @path = File.join(base_path, "views/#{state}.#{handler}") #base_path defined in Cells::Generators::Base.
8
+ template "view.#{handler}", @path
9
+ end
10
+ end
11
+
12
+ private
13
+ def handler
14
+ raise "Please implement #handler in your view generator and return something like `:erb`."
15
+ end
16
+ end
17
+ end
18
+ end
@@ -9,7 +9,7 @@ class BassistCell < Cell::Rails
9
9
  end
10
10
 
11
11
  def provoke
12
- controller.config.relative_url_root = "" if Cell.rails3_0?
12
+ controller.config.relative_url_root = "" if Cell.rails_version.~ 3.0
13
13
 
14
14
  render
15
15
  end
@@ -0,0 +1,2 @@
1
+ %p
2
+ = yield