cells 3.10.1 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
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