best_in_place 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.travis.yml +0 -1
  2. data/README.md +54 -46
  3. data/lib/assets/javascripts/best_in_place.js +42 -0
  4. data/lib/best_in_place.rb +4 -0
  5. data/lib/best_in_place/controller_extensions.rb +1 -1
  6. data/lib/best_in_place/display_methods.rb +24 -2
  7. data/lib/best_in_place/helper.rb +26 -9
  8. data/lib/best_in_place/utils.rb +1 -1
  9. data/lib/best_in_place/version.rb +1 -1
  10. data/spec/helpers/best_in_place_spec.rb +46 -1
  11. data/spec/integration/double_init_spec.rb +2 -1
  12. data/spec/integration/js_spec.rb +178 -8
  13. data/spec/support/retry_on_timeout.rb +10 -0
  14. data/test_app/app/assets/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  15. data/test_app/app/assets/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  16. data/test_app/app/assets/images/ui-bg_flat_10_000000_40x100.png +0 -0
  17. data/test_app/app/assets/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  18. data/test_app/app/assets/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  19. data/test_app/app/assets/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  20. data/test_app/app/assets/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  21. data/test_app/app/assets/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  22. data/test_app/app/assets/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  23. data/test_app/app/assets/images/ui-icons_222222_256x240.png +0 -0
  24. data/test_app/app/assets/images/ui-icons_228ef1_256x240.png +0 -0
  25. data/test_app/app/assets/images/ui-icons_ef8c08_256x240.png +0 -0
  26. data/test_app/app/assets/images/ui-icons_ffd27a_256x240.png +0 -0
  27. data/test_app/app/assets/images/ui-icons_ffffff_256x240.png +0 -0
  28. data/test_app/app/assets/javascripts/application.js +25 -55
  29. data/test_app/app/assets/stylesheets/jquery-ui-1.8.16.custom.css.erb +357 -0
  30. data/test_app/app/assets/stylesheets/{style.css → style.css.erb} +1 -1
  31. data/test_app/app/models/user.rb +1 -0
  32. data/test_app/app/views/layouts/application.html.erb +1 -1
  33. data/test_app/app/views/users/double_init.html.erb +7 -0
  34. data/test_app/app/views/users/show.html.erb +13 -0
  35. data/test_app/config/initializers/default_date_format.rb +2 -0
  36. data/test_app/db/migrate/20111217215935_add_birth_date_to_users.rb +5 -0
  37. data/test_app/db/migrate/20111224181356_add_money_to_user.rb +5 -0
  38. data/test_app/db/schema.rb +3 -1
  39. data/test_app/db/seeds.rb +4 -4
  40. metadata +34 -15
data/.travis.yml CHANGED
@@ -1,6 +1,5 @@
1
1
  rvm:
2
2
  - 1.8.7
3
- - ree
4
3
  - 1.9.2
5
4
  - 1.9.3
6
5
 
data/README.md CHANGED
@@ -21,6 +21,7 @@ The editor works by PUTting the updated value to the server and GETting the upda
21
21
  - Compatible with **textarea**
22
22
  - Compatible with **select** dropdown with custom collections
23
23
  - Compatible with custom boolean values (same usage of **checkboxes**)
24
+ - Compatible with **jQuery UI Datepickers**
24
25
  - Sanitize HTML and trim spaces of user's input on user's choice
25
26
  - Displays server-side **validation** errors
26
27
  - Allows external activator
@@ -28,7 +29,8 @@ The editor works by PUTting the updated value to the server and GETting the upda
28
29
  - Autogrowing textarea
29
30
  - Helper for generating the best_in_place field only if a condition is satisfied
30
31
  - Provided test helpers to be used in your integration specs
31
- - Custom display methods
32
+ - Custom display methods using a method from your model or an existing rails
33
+ view helper
32
34
 
33
35
  ##Usage of Rails 3 Gem
34
36
 
@@ -42,7 +44,7 @@ Params:
42
44
 
43
45
  Options:
44
46
 
45
- - **:type** It can be only [:input, :textarea, :select, :checkbox] or if undefined it defaults to :input.
47
+ - **:type** It can be only [:input, :textarea, :select, :checkbox, :date] or if undefined it defaults to :input.
46
48
  - **:collection**: In case you are using the :select type then you must specify the collection of values it takes. In case you are
47
49
  using the :checkbox type you can specify the two values it can take, or otherwise they will default to Yes and No.
48
50
  - **:path**: URL to which the updating action will be sent. If not defined it defaults to the :object path.
@@ -66,7 +68,7 @@ condition, is satisfied. Specifically:
66
68
 
67
69
  Say we have something like
68
70
 
69
- <%= best_in_place condition, @user, :name, :type => :input %>
71
+ <%= best_in_place_if condition, @user, :name, :type => :input %>
70
72
 
71
73
  In case *condition* is satisfied, the outcome will be just the same as:
72
74
 
@@ -109,48 +111,41 @@ The key can be a string or an integer.
109
111
  The first value is always the negative boolean value and the second the positive. Structure: `["false value", "true value"]`.
110
112
  If not defined, it will default to *Yes* and *No* options.
111
113
 
112
- ## Controller response and respond_with_bip
114
+ ### Date
113
115
 
114
- Your controller should respond to json as it's the format used by best in
115
- place javascript. A simple example would be:
116
+ <%= best_in_place @user, :birth_date, :type => :date %>
116
117
 
117
- class UserController < ApplicationController
118
- def update
119
- @user = User.find(params[:id])
118
+ With the :date type the input field will be initialized as a datepicker input.
119
+ In order to provide custom options to the datepicker initialization you must
120
+ prepare a `$.datepicker.setDefaults` call with the preferences of your choice.
120
121
 
121
- respond_to do |format|
122
- if @user.update_attributes(params[:user])
123
- format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
124
- format.json { head :ok }
125
- else
126
- format.html { render :action => "edit" }
127
- format.json { render :json => @user.errors.full_messages, :status => :unprocessable_entity }
128
- end
122
+ More information about datepicker and setting defaults can be found
123
+ [here](http://docs.jquery.com/UI/Datepicker/$.datepicker.setDefaults)
124
+
125
+ ## Controller response with respond_with_bip
126
+
127
+ Best in place provides a utility method you should use in your controller in
128
+ order to provide the response that is expected by the javascript side, using
129
+ the :json format. This is a simple example showing an update action using it:
130
+
131
+ def update
132
+ @user = User.find params[:id]
133
+
134
+ respond_to do |format|
135
+ if @user.update_attributes(params[:user])
136
+ format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
137
+ format.json { respond_with_bip(@user) }
138
+ else
139
+ format.html { render :action => "edit" }
140
+ format.json { respond_with_bip(@user) }
129
141
  end
130
142
  end
131
143
  end
132
144
 
133
- If you respond with a json like `{:display_as => "New value to show"}` with
134
- status 200 (ok), then the updated field will show *New value to show* after
135
- being updated. This is needed in order to support the custom display methods,
136
- and it's automatically handled if you use the new method to encapsulate
137
- the responses:
138
-
139
-
140
- respond_to do |format|
141
- if @user.update_attributes(params[:user])
142
- format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
143
- format.json { respond_with_bip(@user) }
144
- else
145
- format.html { render :action => "edit" }
146
- format.json { respond_with_bip(@user) }
147
- end
148
- end
149
145
 
150
- This will be exactly the same as the previous example, but with support to
151
- handle custom display methods.
146
+ ## Custom display methods
152
147
 
153
- ##Using custom display methods
148
+ ### Using `display_as`
154
149
 
155
150
  As of best in place 1.0.3 you can use custom methods in your model in order to
156
151
  decide how a certain field has to be displayed. You can write something like:
@@ -159,17 +154,21 @@ decide how a certain field has to be displayed. You can write something like:
159
154
 
160
155
  Then instead of using `@user.description` to show the actual value, best in
161
156
  place will call `@user.mk_description`. This can be used for any kind of
162
- custom formatting, text with markdown, currency values, etc...
157
+ custom formatting, text with markdown, etc...
163
158
 
164
- Because best in place has no way to call that method in your model from
165
- javascript after a successful update, the only way to display the new correct
166
- value after an edition is to use the provided methods to respond in your
167
- controllers, or implement the same in your own way.
159
+ ### Using `display_with`
168
160
 
169
- If you respond a successful update with a json having a `display_as` key, that
170
- value will be used to update the value in the view. The provided
171
- `respond_with_bip` handles this for you, but if you want you can always
172
- customize this behaviour.
161
+ In practice the most common situation is when you want to use an existing
162
+ helper to render the attribute, like `number_to_currency` or `simple_format`.
163
+ As of version 1.0.4 best in place provides this feature using the
164
+ `display_with` option. You can use it like this:
165
+
166
+ = best_in_place @user, :money, :display_with => :number_to_currency
167
+
168
+ If you want to pass further arguments to the helper you can do it providing an
169
+ additional `helper_options` hash:
170
+
171
+ = best_in_place @user, :money, :display_with => :number_to_currency, :helper_options => {:unit => "€"}
173
172
 
174
173
 
175
174
  ##Non Active Record environments
@@ -277,12 +276,17 @@ thanks to Rails 3.1. Just begin including the gem in your Gemfile:
277
276
  gem "best_in_place"
278
277
 
279
278
  After that, specify the use of the jquery, jquery.purr and best in place
280
- javascripts in your application.js:
279
+ javascripts in your application.js, and optionally specify jquery-ui if
280
+ you want to use jQuery UI datepickers:
281
281
 
282
282
  //= require jquery
283
+ //= require jquery-ui
283
284
  //= require jquery.purr
284
285
  //= require best_in_place
285
286
 
287
+ If you want to use jQuery UI datepickers, you should also install and
288
+ load your preferred jquery-ui CSS file and associated assets.
289
+
286
290
  Then, just add a binding to prepare all best in place fields when the document is ready:
287
291
 
288
292
  $(document).ready(function() {
@@ -315,6 +319,10 @@ You can automatize this installation by doing
315
319
 
316
320
  rails g best_in_place:setup
317
321
 
322
+ If you want to use jQuery UI datepickers, you should also install and
323
+ load jquery-ui.js as well as your preferred jquery-ui CSS file and
324
+ associated assets.
325
+
318
326
  Finally, as for Rails 3.1, just add a binding to prepare all best in place fields when the document is ready:
319
327
 
320
328
  $(document).ready(function() {
@@ -83,6 +83,7 @@ BestInPlaceEditor.prototype = {
83
83
  } else {
84
84
  editor.element.html(this.getValue() != "" ? this.getValue() : this.nil);
85
85
  }
86
+ editor.element.trigger($.Event("best_in_place:update"));
86
87
  },
87
88
 
88
89
  activateForm : function() {
@@ -193,6 +194,8 @@ BestInPlaceEditor.prototype = {
193
194
  loadSuccessCallback : function(data) {
194
195
  var response = $.parseJSON($.trim(data));
195
196
  if (response != null && response.hasOwnProperty("display_as")) {
197
+ this.element.attr("data-original-content", this.element.html());
198
+ this.original_content = this.element.html();
196
199
  this.element.html(response["display_as"]);
197
200
  }
198
201
  this.element.trigger($.Event("ajax:success"), data);
@@ -265,6 +268,45 @@ BestInPlaceEditor.forms = {
265
268
  }
266
269
  },
267
270
 
271
+ "date" : {
272
+ activateForm : function() {
273
+ var that = this,
274
+ output = '<form class="form_in_place" action="javascript:void(0)" style="display:inline;">';
275
+ output += '<input type="text" name="'+ this.attributeName + '" value="' + this.sanitizeValue(this.display_value) + '"';
276
+ if (this.inner_class != null) {
277
+ output += ' class="' + this.inner_class + '"';
278
+ }
279
+ output += '></form>'
280
+ this.element.html(output);
281
+ this.setHtmlAttributes();
282
+ this.element.find('input')[0].select();
283
+ this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
284
+ this.element.find("input").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
285
+
286
+ this.element.find('input')
287
+ .datepicker({
288
+ onClose: function() {
289
+ that.update();
290
+ }
291
+ })
292
+ .datepicker('show');
293
+ },
294
+
295
+ getValue : function() {
296
+ return this.sanitizeValue(this.element.find("input").val());
297
+ },
298
+
299
+ submitHandler : function(event) {
300
+ event.data.editor.update();
301
+ },
302
+
303
+ keyupHandler : function(event) {
304
+ if (event.keyCode == 27) {
305
+ event.data.editor.abort();
306
+ }
307
+ }
308
+ },
309
+
268
310
  "select" : {
269
311
  activateForm : function() {
270
312
  var output = "<form action='javascript:void(0)' style='display:inline;'><select>";
data/lib/best_in_place.rb CHANGED
@@ -6,4 +6,8 @@ require "best_in_place/display_methods"
6
6
 
7
7
  module BestInPlace
8
8
  autoload :TestHelpers, "best_in_place/test_helpers"
9
+
10
+ module ViewHelpers
11
+ extend ActionView::Helpers
12
+ end
9
13
  end
@@ -10,7 +10,7 @@ module BestInPlace
10
10
  updating_attr = params[klass.underscore].keys.first
11
11
 
12
12
  if renderer = BestInPlace::DisplayMethods.lookup(klass, updating_attr)
13
- render :json => {:display_as => obj.send(renderer)}.to_json
13
+ render :json => renderer.render_json(obj)
14
14
  else
15
15
  head :ok
16
16
  end
@@ -2,6 +2,24 @@ module BestInPlace
2
2
  module DisplayMethods
3
3
  extend self
4
4
 
5
+ class Renderer < Struct.new(:opts)
6
+ def render_json(object)
7
+ case opts[:type]
8
+ when :model
9
+ {:display_as => object.send(opts[:method])}.to_json
10
+ when :helper
11
+ value = if opts[:helper_options]
12
+ BestInPlace::ViewHelpers.send(opts[:method], object.send(opts[:attr]), opts[:helper_options])
13
+ else
14
+ BestInPlace::ViewHelpers.send(opts[:method], object.send(opts[:attr]))
15
+ end
16
+ {:display_as => value}.to_json
17
+ else
18
+ {}.to_json
19
+ end
20
+ end
21
+ end
22
+
5
23
  @@table = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) }
6
24
 
7
25
  def lookup(klass, attr)
@@ -9,8 +27,12 @@ module BestInPlace
9
27
  foo == {} ? nil : foo
10
28
  end
11
29
 
12
- def add(klass, attr, display_as)
13
- @@table[klass.to_s][attr.to_s] = display_as.to_sym
30
+ def add_model_method(klass, attr, display_as)
31
+ @@table[klass.to_s][attr.to_s] = Renderer.new :method => display_as.to_sym, :type => :model
32
+ end
33
+
34
+ def add_helper_method(klass, attr, helper_method, helper_options = nil)
35
+ @@table[klass.to_s][attr.to_s] = Renderer.new :method => helper_method.to_sym, :type => :helper, :attr => attr, :helper_options => helper_options
14
36
  end
15
37
  end
16
38
  end
@@ -2,6 +2,14 @@ module BestInPlace
2
2
  module BestInPlaceHelpers
3
3
 
4
4
  def best_in_place(object, field, opts = {})
5
+ if opts[:display_as] && opts[:display_with]
6
+ raise ArgumentError, "Can't use both 'display_as' and 'display_with' options at the same time"
7
+ end
8
+
9
+ if opts[:display_with] && !ViewHelpers.respond_to?(opts[:display_with])
10
+ raise ArgumentError, "Can't find helper #{opts[:display_with]}"
11
+ end
12
+
5
13
  opts[:type] ||= :input
6
14
  opts[:collection] ||= []
7
15
  field = field.to_s
@@ -24,21 +32,21 @@ module BestInPlace
24
32
  end
25
33
  out = "<span class='best_in_place'"
26
34
  out << " id='#{BestInPlace::Utils.build_best_in_place_id(object, field)}'"
27
- out << " data-url='#{opts[:path].blank? ? url_for(object).to_s : url_for(opts[:path])}'"
35
+ out << " data-url='#{opts[:path].blank? ? url_for(object) : url_for(opts[:path])}'"
28
36
  out << " data-object='#{object.class.to_s.gsub("::", "_").underscore}'"
29
37
  out << " data-collection='#{collection.gsub(/'/, "&#39;")}'" unless collection.blank?
30
38
  out << " data-attribute='#{field}'"
31
39
  out << " data-activator='#{opts[:activator]}'" unless opts[:activator].blank?
32
- out << " data-nil='#{opts[:nil].to_s}'" unless opts[:nil].blank?
33
- out << " data-type='#{opts[:type].to_s}'"
34
- out << " data-inner-class='#{opts[:inner_class].to_s}'" if opts[:inner_class]
40
+ out << " data-nil='#{opts[:nil]}'" unless opts[:nil].blank?
41
+ out << " data-type='#{opts[:type]}'"
42
+ out << " data-inner-class='#{opts[:inner_class]}'" if opts[:inner_class]
35
43
  out << " data-html-attrs='#{opts[:html_attrs].to_json}'" unless opts[:html_attrs].blank?
36
- out << " data-original-content='#{object.send(field)}'" if opts[:display_as]
44
+ out << " data-original-content='#{object.send(field)}'" if opts[:display_as] || opts[:display_with]
37
45
  if !opts[:sanitize].nil? && !opts[:sanitize]
38
46
  out << " data-sanitize='false'>"
39
- out << sanitize(value.to_s, :tags => %w(b i u s a strong em p h1 h2 h3 h4 h5 ul li ol hr pre span img br), :attributes => %w(id class href))
47
+ out << sanitize(value, :tags => %w(b i u s a strong em p h1 h2 h3 h4 h5 ul li ol hr pre span img br), :attributes => %w(id class href))
40
48
  else
41
- out << ">#{sanitize(value.to_s, :tags => nil, :attributes => nil)}"
49
+ out << ">#{sanitize(value, :tags => nil, :attributes => nil)}"
42
50
  end
43
51
  out << "</span>"
44
52
  raw out
@@ -55,10 +63,19 @@ module BestInPlace
55
63
  private
56
64
  def build_value_for(object, field, opts)
57
65
  if opts[:display_as]
58
- BestInPlace::DisplayMethods.add(object.class.to_s, field, opts[:display_as])
66
+ BestInPlace::DisplayMethods.add_model_method(object.class.to_s, field, opts[:display_as])
59
67
  object.send(opts[:display_as]).to_s
68
+
69
+ elsif opts[:display_with]
70
+ BestInPlace::DisplayMethods.add_helper_method(object.class.to_s, field, opts[:display_with], opts[:helper_options])
71
+ if opts[:helper_options]
72
+ BestInPlace::ViewHelpers.send(opts[:display_with], object.send(field), opts[:helper_options])
73
+ else
74
+ BestInPlace::ViewHelpers.send(opts[:display_with], object.send(field))
75
+ end
76
+
60
77
  else
61
- object.send(field).blank? ? "" : object.send(field).to_s
78
+ object.send(field).to_s.presence || ""
62
79
  end
63
80
  end
64
81
  end
@@ -7,7 +7,7 @@ module BestInPlace
7
7
  end
8
8
 
9
9
  id = "best_in_place_#{object.class.to_s.demodulize.underscore}"
10
- id << "_#{object.id}" if object.class.ancestors.include?(ActiveRecord::Base)
10
+ id << "_#{object.id}" if object.class.ancestors.include?(ActiveModel::Serializers::JSON)
11
11
  id << "_#{field}"
12
12
  id
13
13
  end
@@ -1,3 +1,3 @@
1
1
  module BestInPlace
2
- VERSION = "1.0.3"
2
+ VERSION = "1.0.4"
3
3
  end
@@ -11,7 +11,9 @@ describe BestInPlace::BestInPlaceHelpers do
11
11
  :zip => "25123",
12
12
  :country => "2",
13
13
  :receive_email => false,
14
- :description => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus a lectus et lacus ultrices auctor. Morbi aliquet convallis tincidunt. Praesent enim libero, iaculis at commodo nec, fermentum a dolor. Quisque eget eros id felis lacinia faucibus feugiat et ante. Aenean justo nisi, aliquam vel egestas vel, porta in ligula. Etiam molestie, lacus eget tincidunt accumsan, elit justo rhoncus urna, nec pretium neque mi et lorem. Aliquam posuere, dolor quis pulvinar luctus, felis dolor tincidunt leo, eget pretium orci purus ac nibh. Ut enim sem, suscipit ac elementum vitae, sodales vel sem."
14
+ :birth_date => Time.now.utc.to_date,
15
+ :description => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus a lectus et lacus ultrices auctor. Morbi aliquet convallis tincidunt. Praesent enim libero, iaculis at commodo nec, fermentum a dolor. Quisque eget eros id felis lacinia faucibus feugiat et ante. Aenean justo nisi, aliquam vel egestas vel, porta in ligula. Etiam molestie, lacus eget tincidunt accumsan, elit justo rhoncus urna, nec pretium neque mi et lorem. Aliquam posuere, dolor quis pulvinar luctus, felis dolor tincidunt leo, eget pretium orci purus ac nibh. Ut enim sem, suscipit ac elementum vitae, sodales vel sem.",
16
+ :money => 150
15
17
  end
16
18
 
17
19
  it "should generate a proper span" do
@@ -20,6 +22,10 @@ describe BestInPlace::BestInPlaceHelpers do
20
22
  span.should_not be_empty
21
23
  end
22
24
 
25
+ it "should not allow both display_as and display_with option" do
26
+ lambda { helper.best_in_place(@user, :money, :display_with => :number_to_currency, :display_as => :custom) }.should raise_error(ArgumentError)
27
+ end
28
+
23
29
  describe "general properties" do
24
30
  before do
25
31
  nk = Nokogiri::HTML.parse(helper.best_in_place @user, :name)
@@ -133,6 +139,26 @@ describe BestInPlace::BestInPlaceHelpers do
133
139
  span.text.should == "the result"
134
140
  end
135
141
  end
142
+
143
+ describe "display_with" do
144
+ it "should render the money with the given view helper" do
145
+ out = helper.best_in_place @user, :money, :display_with => :number_to_currency
146
+ nk = Nokogiri::HTML.parse(out)
147
+ span = nk.css("span")
148
+ span.text.should == "$150.00"
149
+ end
150
+
151
+ it "should raise an error if the given helper can't be found" do
152
+ lambda { helper.best_in_place @user, :money, :display_with => :fk_number_to_currency }.should raise_error(ArgumentError)
153
+ end
154
+
155
+ it "should call the helper method with the given arguments" do
156
+ out = helper.best_in_place @user, :money, :display_with => :number_to_currency, :helper_options => {:unit => "º"}
157
+ nk = Nokogiri::HTML.parse(out)
158
+ span = nk.css("span")
159
+ span.text.should == "º150.00"
160
+ end
161
+ end
136
162
  end
137
163
 
138
164
 
@@ -155,6 +181,25 @@ describe BestInPlace::BestInPlaceHelpers do
155
181
  end
156
182
  end
157
183
 
184
+ context "with a date attribute" do
185
+ before do
186
+ nk = Nokogiri::HTML.parse(helper.best_in_place @user, :birth_date, :type => :date)
187
+ @span = nk.css("span")
188
+ end
189
+
190
+ it "should render the date as text" do
191
+ @span.text.should == @user.birth_date.to_date.to_s
192
+ end
193
+
194
+ it "should have a date data-type" do
195
+ @span.attribute("data-type").value.should == "date"
196
+ end
197
+
198
+ it "should have no data-collection" do
199
+ @span.attribute("data-collection").should be_nil
200
+ end
201
+ end
202
+
158
203
  context "with a boolean attribute" do
159
204
  before do
160
205
  nk = Nokogiri::HTML.parse(helper.best_in_place @user, :receive_email, :type => :checkbox)