cwninja-facebooker 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/CHANGELOG.txt +0 -0
  2. data/COPYING +19 -0
  3. data/History.txt +16 -0
  4. data/Manifest.txt +114 -0
  5. data/README +46 -0
  6. data/README.txt +81 -0
  7. data/Rakefile +76 -0
  8. data/TODO.txt +10 -0
  9. data/generators/facebook/facebook_generator.rb +14 -0
  10. data/generators/facebook/templates/config/facebooker.yml +46 -0
  11. data/generators/facebook/templates/public/javascripts/facebooker.js +106 -0
  12. data/generators/facebook_controller/USAGE +33 -0
  13. data/generators/facebook_controller/facebook_controller_generator.rb +40 -0
  14. data/generators/facebook_controller/templates/controller.rb +7 -0
  15. data/generators/facebook_controller/templates/functional_test.rb +11 -0
  16. data/generators/facebook_controller/templates/helper.rb +2 -0
  17. data/generators/facebook_controller/templates/view.fbml.erb +2 -0
  18. data/generators/facebook_controller/templates/view.html.erb +2 -0
  19. data/generators/facebook_publisher/facebook_publisher_generator.rb +14 -0
  20. data/generators/facebook_publisher/templates/create_facebook_templates.rb +15 -0
  21. data/generators/facebook_publisher/templates/publisher.rb +3 -0
  22. data/generators/facebook_scaffold/USAGE +27 -0
  23. data/generators/facebook_scaffold/facebook_scaffold_generator.rb +118 -0
  24. data/generators/facebook_scaffold/templates/controller.rb +93 -0
  25. data/generators/facebook_scaffold/templates/facebook_style.css +2579 -0
  26. data/generators/facebook_scaffold/templates/functional_test.rb +89 -0
  27. data/generators/facebook_scaffold/templates/helper.rb +2 -0
  28. data/generators/facebook_scaffold/templates/layout.fbml.erb +6 -0
  29. data/generators/facebook_scaffold/templates/layout.html.erb +17 -0
  30. data/generators/facebook_scaffold/templates/style.css +74 -0
  31. data/generators/facebook_scaffold/templates/view_edit.fbml.erb +13 -0
  32. data/generators/facebook_scaffold/templates/view_edit.html.erb +18 -0
  33. data/generators/facebook_scaffold/templates/view_index.fbml.erb +24 -0
  34. data/generators/facebook_scaffold/templates/view_index.html.erb +24 -0
  35. data/generators/facebook_scaffold/templates/view_new.fbml.erb +12 -0
  36. data/generators/facebook_scaffold/templates/view_new.html.erb +17 -0
  37. data/generators/facebook_scaffold/templates/view_show.fbml.erb +10 -0
  38. data/generators/facebook_scaffold/templates/view_show.html.erb +10 -0
  39. data/generators/publisher/publisher_generator.rb +14 -0
  40. data/generators/xd_receiver/templates/xd_receiver.html +10 -0
  41. data/generators/xd_receiver/xd_receiver_generator.rb +9 -0
  42. data/init.rb +70 -0
  43. data/install.rb +12 -0
  44. data/lib/facebooker.rb +160 -0
  45. data/lib/facebooker/adapters/adapter_base.rb +87 -0
  46. data/lib/facebooker/adapters/bebo_adapter.rb +75 -0
  47. data/lib/facebooker/adapters/facebook_adapter.rb +48 -0
  48. data/lib/facebooker/admin.rb +28 -0
  49. data/lib/facebooker/batch_request.rb +44 -0
  50. data/lib/facebooker/data.rb +57 -0
  51. data/lib/facebooker/feed.rb +78 -0
  52. data/lib/facebooker/logging.rb +51 -0
  53. data/lib/facebooker/model.rb +123 -0
  54. data/lib/facebooker/models/affiliation.rb +10 -0
  55. data/lib/facebooker/models/album.rb +11 -0
  56. data/lib/facebooker/models/applicationproperties.rb +39 -0
  57. data/lib/facebooker/models/cookie.rb +10 -0
  58. data/lib/facebooker/models/education_info.rb +11 -0
  59. data/lib/facebooker/models/event.rb +26 -0
  60. data/lib/facebooker/models/friend_list.rb +14 -0
  61. data/lib/facebooker/models/group.rb +35 -0
  62. data/lib/facebooker/models/info_item.rb +10 -0
  63. data/lib/facebooker/models/info_section.rb +10 -0
  64. data/lib/facebooker/models/location.rb +8 -0
  65. data/lib/facebooker/models/notifications.rb +17 -0
  66. data/lib/facebooker/models/page.rb +27 -0
  67. data/lib/facebooker/models/photo.rb +10 -0
  68. data/lib/facebooker/models/tag.rb +12 -0
  69. data/lib/facebooker/models/user.rb +369 -0
  70. data/lib/facebooker/models/work_info.rb +9 -0
  71. data/lib/facebooker/parser.rb +547 -0
  72. data/lib/facebooker/rails/controller.rb +280 -0
  73. data/lib/facebooker/rails/facebook_asset_path.rb +18 -0
  74. data/lib/facebooker/rails/facebook_form_builder.rb +112 -0
  75. data/lib/facebooker/rails/facebook_pretty_errors.rb +14 -0
  76. data/lib/facebooker/rails/facebook_request_fix.rb +24 -0
  77. data/lib/facebooker/rails/facebook_session_handling.rb +69 -0
  78. data/lib/facebooker/rails/facebook_url_helper.rb +191 -0
  79. data/lib/facebooker/rails/facebook_url_rewriting.rb +39 -0
  80. data/lib/facebooker/rails/helpers.rb +642 -0
  81. data/lib/facebooker/rails/helpers/fb_connect.rb +34 -0
  82. data/lib/facebooker/rails/profile_publisher_extensions.rb +42 -0
  83. data/lib/facebooker/rails/publisher.rb +511 -0
  84. data/lib/facebooker/rails/routing.rb +49 -0
  85. data/lib/facebooker/rails/test_helpers.rb +88 -0
  86. data/lib/facebooker/rails/utilities.rb +22 -0
  87. data/lib/facebooker/server_cache.rb +24 -0
  88. data/lib/facebooker/service.rb +83 -0
  89. data/lib/facebooker/session.rb +564 -0
  90. data/lib/facebooker/version.rb +9 -0
  91. data/lib/net/http_multipart_post.rb +123 -0
  92. data/lib/tasks/facebooker.rake +18 -0
  93. data/lib/tasks/tunnel.rake +46 -0
  94. data/rails/init.rb +1 -0
  95. data/setup.rb +1585 -0
  96. data/templates/layout.erb +24 -0
  97. data/test/adapters_test.rb +98 -0
  98. data/test/batch_request_test.rb +82 -0
  99. data/test/event_test.rb +15 -0
  100. data/test/facebook_admin_test.rb +76 -0
  101. data/test/facebook_cache_test.rb +44 -0
  102. data/test/facebook_data_test.rb +86 -0
  103. data/test/facebooker_test.rb +855 -0
  104. data/test/fixtures/multipart_post_body_with_only_parameters.txt +33 -0
  105. data/test/fixtures/multipart_post_body_with_single_file.txt +38 -0
  106. data/test/fixtures/multipart_post_body_with_single_file_that_has_nil_key.txt +38 -0
  107. data/test/http_multipart_post_test.rb +54 -0
  108. data/test/logging_test.rb +43 -0
  109. data/test/model_test.rb +91 -0
  110. data/test/publisher_test.rb +458 -0
  111. data/test/rails_integration_test.rb +1221 -0
  112. data/test/session_test.rb +613 -0
  113. data/test/test_helper.rb +60 -0
  114. data/test/user_test.rb +270 -0
  115. metadata +185 -0
@@ -0,0 +1,69 @@
1
+ module ActionController
2
+ class CgiRequest
3
+ alias :initialize_aliased_by_facebooker :initialize
4
+
5
+ def initialize(cgi, session_options = {})
6
+ initialize_aliased_by_facebooker(cgi, session_options)
7
+ @cgi.instance_variable_set("@request_params", request_parameters.merge(query_parameters))
8
+ end
9
+
10
+ DEFAULT_SESSION_OPTIONS[:cookie_only] = false
11
+ end
12
+ end
13
+
14
+ module ActionController
15
+ class RackRequest < AbstractRequest #:nodoc:
16
+ alias :initialize_aliased_by_facebooker :initialize
17
+
18
+ def initialize(cgi, session_options = {})
19
+ initialize_aliased_by_facebooker(cgi, session_options)
20
+ @cgi.instance_variable_set("@request_params", request_parameters.merge(query_parameters))
21
+ end
22
+ end
23
+ end
24
+
25
+ class CGI
26
+ class Session
27
+ private
28
+ alias :initialize_aliased_by_facebooker :initialize
29
+ attr_reader :request, :initialization_options
30
+
31
+ def initialize(request, option={})
32
+ @request = request
33
+ @initialization_options = option
34
+ option['session_id'] = set_session_id
35
+ initialize_aliased_by_facebooker(request, option)
36
+ end
37
+
38
+ def set_session_id
39
+ if session_key_should_be_set_with_facebook_session_key?
40
+ request_parameters[facebook_session_key]
41
+ else
42
+ request_parameters[session_key]
43
+ end
44
+ end
45
+
46
+ def request_parameters
47
+ request.instance_variable_get("@request_params")
48
+ end
49
+
50
+ def session_key_should_be_set_with_facebook_session_key?
51
+ request_parameters[session_key].blank? && !request_parameters[facebook_session_key].blank?
52
+ end
53
+
54
+ def session_key
55
+ initialization_options['session_key'] || '_session_id'
56
+ end
57
+
58
+ def facebook_session_key
59
+ 'fb_sig_session_key'
60
+ end
61
+
62
+ alias :create_new_id_aliased_by_facebooker :create_new_id
63
+
64
+ def create_new_id
65
+ @new_session = true
66
+ @session_id || create_new_id_aliased_by_facebooker
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,191 @@
1
+ # Extends the ActionView::Helpers::UrlHelper module. See it for details on
2
+ # the usual url helper methods: url_for, link_to, button_to, etc.
3
+ #
4
+ # Mostly, the changes sanitize javascript into facebook javascript.
5
+ # It sanitizes link_to solely by altering the private methods:
6
+ # convert_options_to_javascript!, confirm_javascript_function, and
7
+ # method_javascript_function. For button_to, it alters button_to
8
+ # itself, as well as confirm_javascript_function. No other methods
9
+ # need to be changed because of Facebook javascript.
10
+ #
11
+ # For button_to and link_to, adds alternate confirm options for facebook.
12
+ # ==== Options
13
+ # * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
14
+ # prompt with the question specified.
15
+ #
16
+ # Example:
17
+ # # Generates: <a href="http://rubyforge.org/projects/facebooker" onclick="
18
+ # # var dlg = new Dialog().showChoice('Please Confirm', 'Go to Facebooker?').setStyle();
19
+ # # var a=this;dlg.onconfirm = function() {
20
+ # # document.setLocation(a.getHref());
21
+ # # }; return false;">Facebooker</a>
22
+ # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>"Go to Facebooker?")
23
+ #
24
+ # Alternatively, options[:confirm] may be specified.
25
+ # See the Facebook page http://wiki.developers.facebook.com/index.php/FBJS.
26
+ # These options are:
27
+ # <tt>:title</tt>:: Specifies the title of the Facebook dialog. Default is "Please Confirm".
28
+ # <tt>:content</tt>:: Specifies the title of the Facebook dialog. Default is "Are you sure?".
29
+ #
30
+ # Example:
31
+ # # Generates: <a href="http://rubyforge.org/projects/facebooker" onclick="
32
+ # # var dlg = new Dialog().showChoice('the page says:', 'Go to Facebooker?').setStyle();
33
+ # # var a=this;dlg.onconfirm = function() {
34
+ # # document.setLocation(a.getHref());
35
+ # # }; return false;">Facebooker</a>
36
+ # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>{:title=>"the page says:", :content=>"Go to Facebooker?"})
37
+ #
38
+ # Any other options passed are assumed to be css styles.
39
+ # Again, see the Facebook page http://wiki.developers.facebook.com/index.php/FBJS.
40
+ #
41
+ # Example:
42
+ # # Generates: <a href="http://rubyforge.org/projects/facebooker" onclick="
43
+ # # var dlg = new Dialog().showChoice('the page says:', 'Are you sure?').setStyle({color: 'pink', width: '200px'});
44
+ # # var a=this;dlg.onconfirm = function() {
45
+ # # document.setLocation(a.getHref());
46
+ # # }; return false;">Facebooker</a>
47
+ # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>{:title=>"the page says:, :color=>"pink", :width=>"200px"})
48
+ module ActionView
49
+ module Helpers
50
+ module UrlHelper
51
+ # Alters one and only one line of the Rails button_to. See below.
52
+ def button_to_with_facebooker(name, options={}, html_options = {})
53
+ if !request_comes_from_facebook?
54
+ button_to_without_facebooker(name,options,html_options)
55
+ else
56
+ html_options = html_options.stringify_keys
57
+ convert_boolean_attributes!(html_options, %w( disabled ))
58
+
59
+ method_tag = ''
60
+ if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
61
+ method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
62
+ end
63
+
64
+ form_method = method.to_s == 'get' ? 'get' : 'post'
65
+
66
+ request_token_tag = ''
67
+ if form_method == 'post' && protect_against_forgery?
68
+ request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
69
+ end
70
+
71
+ if confirm = html_options.delete("confirm")
72
+ # this line is the only change => html_options["onclick"] = "return #{confirm_javascript_function(confirm)}"
73
+ html_options["onclick"] = "#{confirm_javascript_function(confirm, 'a.getForm().submit();')}return false;"
74
+ end
75
+
76
+ url = options.is_a?(String) ? options : self.url_for(options)
77
+ name ||= url
78
+
79
+ html_options.merge!("type" => "submit", "value" => name)
80
+
81
+ "<form method=\"#{form_method}\" action=\"#{escape_once url}\" class=\"button-to\"><div>" +
82
+ method_tag + tag("input", html_options) + request_token_tag + "</div></form>"
83
+ end
84
+ end
85
+
86
+ alias_method_chain :button_to, :facebooker
87
+
88
+ private
89
+
90
+ # Altered to throw an error on :popup and sanitize the javascript
91
+ # for Facebook.
92
+ def convert_options_to_javascript_with_facebooker!(html_options, url ='')
93
+ if !request_comes_from_facebook?
94
+ convert_options_to_javascript_without_facebooker!(html_options,url)
95
+ else
96
+ confirm, popup = html_options.delete("confirm"), html_options.delete("popup")
97
+
98
+ method, href = html_options.delete("method"), html_options['href']
99
+
100
+ html_options["onclick"] = case
101
+ when popup
102
+ raise ActionView::ActionViewError, "You can't use :popup"
103
+ when method # or maybe (confirm and method)
104
+ "#{method_javascript_function(method, url, href, confirm)}return false;"
105
+ when confirm # and only confirm
106
+ "#{confirm_javascript_function(confirm)}return false;"
107
+ else
108
+ html_options["onclick"]
109
+ end
110
+ end
111
+ end
112
+
113
+ alias_method_chain :convert_options_to_javascript!, :facebooker
114
+
115
+
116
+ # Overrides a private method that link_to calls via convert_options_to_javascript! and
117
+ # also, button_to calls directly. For Facebook, confirm can be a hash of options to
118
+ # stylize the Facebook dialog. Takes :title, :content, :style options. See
119
+ # the Facebook page http://wiki.developers.facebook.com/index.php/FBJS for valid
120
+ # style formats like "color: 'black', background: 'white'" or like "'color','black'".
121
+ #
122
+ # == Examples ==
123
+ #
124
+ # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>"Go to Facebooker?")
125
+ # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>{:title=>"the page says:, :content=>"Go to Facebooker?"})
126
+ # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>{:title=>"the page says:, :content=>"Go to Facebooker?", :color=>"pink"})
127
+ def confirm_javascript_function_with_facebooker(confirm, fun = nil)
128
+ if !request_comes_from_facebook?
129
+ confirm_javascript_function_without_facebooker(confirm)
130
+ else
131
+ if(confirm.is_a?(Hash))
132
+ confirm_options = confirm.stringify_keys
133
+ title = confirm_options.delete("title") || "Please Confirm"
134
+ content = confirm_options.delete("content") || "Are you sure?"
135
+ style = confirm_options.empty? ? "" : convert_options_to_css(confirm_options)
136
+ else
137
+ title,content,style = 'Please Confirm', confirm, ""
138
+ end
139
+
140
+ "var dlg = new Dialog().showChoice('#{escape_javascript(title.to_s)}','#{escape_javascript(content.to_s)}').setStyle(#{style});"+
141
+ "var a=this;dlg.onconfirm = function() { #{fun ? fun : 'document.setLocation(a.getHref());'} };"
142
+ end
143
+ end
144
+
145
+ alias_method_chain :confirm_javascript_function, :facebooker
146
+
147
+ def convert_options_to_css(options)
148
+ key_pair = options.shift
149
+ style = "{#{key_pair[0]}: '#{key_pair[1]}'"
150
+ for key in options.keys
151
+ style << ", #{key}: '#{options[key]}'"
152
+ end
153
+ style << "}"
154
+ end
155
+
156
+ # Dynamically creates a form for link_to with method. Calls confirm_javascript_function if and
157
+ # only if (confirm && method) for link_to
158
+ def method_javascript_function_with_facebooker(method, url = '', href = nil, confirm = nil)
159
+ if !request_comes_from_facebook?
160
+ method_javascript_function_without_facebooker(method,url,href)
161
+ else
162
+ action = (href && url.size > 0) ? "'#{url}'" : 'a.getHref()'
163
+ submit_function =
164
+ "var f = document.createElement('form'); f.setStyle('display','none'); " +
165
+ "a.getParentNode().appendChild(f); f.setMethod('POST'); f.setAction(#{action});"
166
+
167
+ unless method == :post
168
+ submit_function << "var m = document.createElement('input'); m.setType('hidden'); "
169
+ submit_function << "m.setName('_method'); m.setValue('#{method}'); f.appendChild(m);"
170
+ end
171
+
172
+ if protect_against_forgery?
173
+ submit_function << "var s = document.createElement('input'); s.setType('hidden'); "
174
+ submit_function << "s.setName('#{request_forgery_protection_token}'); s.setValue('#{escape_javascript form_authenticity_token}'); f.appendChild(s);"
175
+ end
176
+ submit_function << "f.submit();"
177
+
178
+ if(confirm)
179
+ confirm_javascript_function(confirm, submit_function)
180
+ else
181
+ "var a=this;" + submit_function
182
+ end
183
+ end
184
+ end
185
+
186
+ alias_method_chain :method_javascript_function, :facebooker
187
+
188
+ end
189
+ end
190
+ end
191
+
@@ -0,0 +1,39 @@
1
+ module ::ActionController
2
+ class AbstractRequest
3
+ def relative_url_root
4
+ Facebooker.path_prefix
5
+ end
6
+ end
7
+
8
+ class Base
9
+ def self.relative_url_root
10
+ Facebooker.path_prefix
11
+ end
12
+ end
13
+
14
+ class UrlRewriter
15
+ RESERVED_OPTIONS << :canvas
16
+ def link_to_new_canvas?
17
+ @request.parameters["fb_sig_in_new_facebook"] == "1"
18
+ end
19
+ def link_to_canvas?(params, options)
20
+ option_override = options[:canvas]
21
+ return false if option_override == false # important to check for false. nil should use default behavior
22
+ option_override || @request.parameters["fb_sig_in_canvas"] == "1" || @request.parameters[:fb_sig_in_canvas] == "1"
23
+ end
24
+
25
+ def rewrite_url_with_facebooker(*args)
26
+ options = args.first.is_a?(Hash) ? args.first : args.last
27
+ is_link_to_canvas = link_to_canvas?(@request.request_parameters, options)
28
+ if is_link_to_canvas && !options.has_key?(:host)
29
+ options[:host] = Facebooker.canvas_server_base
30
+ end
31
+ options.delete(:canvas)
32
+ Facebooker.request_for_canvas(is_link_to_canvas) do
33
+ rewrite_url_without_facebooker(*args)
34
+ end
35
+ end
36
+
37
+ alias_method_chain :rewrite_url, :facebooker
38
+ end
39
+ end
@@ -0,0 +1,642 @@
1
+ module Facebooker
2
+ module Rails
3
+
4
+ # Facebook specific helpers for creating FBML
5
+ #
6
+ # All helpers that take a user as a parameter will get the Facebook UID from the facebook_id attribute if it exists.
7
+ # It will use to_s if the facebook_id attribute is not present.
8
+ #
9
+ module Helpers
10
+
11
+ include Facebooker::Rails::Helpers::FbConnect
12
+
13
+ # Create an fb:dialog
14
+ # id must be a unique name e.g. "my_dialog"
15
+ # cancel_button is true or false
16
+ def fb_dialog( id, cancel_button, &block )
17
+ content = capture(&block)
18
+ concat( content_tag("fb:dialog", content, {:id => id, :cancel_button => cancel_button}), block.binding )
19
+ end
20
+
21
+ def fb_dialog_title( title )
22
+ content_tag "fb:dialog-title", title
23
+ end
24
+
25
+ def fb_dialog_content( &block )
26
+ content = capture(&block)
27
+ concat( content_tag("fb:dialog-content", content), block.binding )
28
+ end
29
+
30
+ def fb_dialog_button( type, value, options={} )
31
+ options.assert_valid_keys FB_DIALOG_BUTTON_VALID_OPTION_KEYS
32
+ options.merge! :type => type, :value => value
33
+ tag "fb:dialog-button", options
34
+ end
35
+
36
+ FB_DIALOG_BUTTON_VALID_OPTION_KEYS = [:close_dialog, :href, :form_id, :clickrewriteurl, :clickrewriteid, :clickrewriteform]
37
+
38
+ # Create an fb:request-form without a selector
39
+ #
40
+ # The block passed to this tag is used as the content of the form
41
+ #
42
+ # The message param is the name sent to content_for that specifies the body of the message
43
+ #
44
+ # For example,
45
+ #
46
+ # <% content_for("invite_message") do %>
47
+ # This gets sent in the invite. <%= fb_req_choice("with a button!",new_poke_path) %>
48
+ # <% end %>
49
+ # <% fb_request_form("Poke","invite_message",create_poke_path) do %>
50
+ # Send a poke to: <%= fb_friend_selector %> <br />
51
+ # <%= fb_request_form_submit %>
52
+ # <% end %>
53
+ def fb_request_form(type,message_param,url,options={},&block)
54
+ content = capture(&block)
55
+ message = @template.instance_variable_get("@content_for_#{message_param}")
56
+ concat(content_tag("fb:request-form", content + token_tag,
57
+ {:action=>url,:method=>"post",:invite=>true,:type=>type,:content=>message}.merge(options)),
58
+ block.binding)
59
+ end
60
+
61
+ # Create a submit button for an <fb:request-form>
62
+ # If the request is for an individual user you can optionally
63
+ # Provide the user and a label for the request button.
64
+ # For example
65
+ # <% content_for("invite_user") do %>
66
+ # This gets sent in the invite. <%= fb_req_choice("Come join us!",new_invite_path) %>
67
+ # <% end %>
68
+ # <% fb_request_form("Invite","invite_user",create_invite_path) do %>
69
+ # Invite <%= fb_name(@facebook_user.friends.first.id)%> to the party <br />
70
+ # <%= fb_request_form_submit(@facebook_user.friends.first.id,"Invite %n") %>
71
+ # <% end %>
72
+ # <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:request-form-submit for options
73
+ def fb_request_form_submit(options={})
74
+ tag("fb:request-form-submit",stringify_vals(options))
75
+ end
76
+
77
+
78
+ # Create an fb:request-form with an fb_multi_friend_selector inside
79
+ #
80
+ # The content of the block are used as the message on the form,
81
+ #
82
+ # For example:
83
+ # <% fb_multi_friend_request("Poke","Choose some friends to Poke",create_poke_path) do %>
84
+ # If you select some friends, they will see this message.
85
+ # <%= fb_req_choice("They will get this button, too",new_poke_path) %>
86
+ # <% end %>
87
+ def fb_multi_friend_request(type,friend_selector_message,url,&block)
88
+ content = capture(&block)
89
+ concat(content_tag("fb:request-form",
90
+ fb_multi_friend_selector(friend_selector_message) + token_tag,
91
+ {:action=>url,:method=>"post",:invite=>true,:type=>type,:content=>content}
92
+ ),
93
+ block.binding)
94
+ end
95
+
96
+ # Render an <fb:friend-selector> element
97
+ # <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:friend-selector for options
98
+ #
99
+ def fb_friend_selector(options={})
100
+ tag("fb:friend-selector",stringify_vals(options))
101
+ end
102
+
103
+ # Render an <fb:multi-friend-input> element
104
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:multi-friend-input for options
105
+ def fb_multi_friend_input(options={})
106
+ tag "fb:multi-friend-input",stringify_vals(options)
107
+ end
108
+
109
+ # Render an <fb:multi-friend-selector> with the passed in welcome message
110
+ # Full version shows all profile pics for friends.
111
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:multi-friend-selector for options
112
+ # <em> Note: </em> I don't think the block is used here.
113
+ def fb_multi_friend_selector(message,options={},&block)
114
+ options = options.dup
115
+ tag("fb:multi-friend-selector",stringify_vals({:showborder=>false,:actiontext=>message,:max=>20}.merge(options)))
116
+ end
117
+
118
+ # Render a condensed <fb:multi-friend-selector> with the passed in welcome message
119
+ # Condensed version show checkboxes for each friend.
120
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:multi-friend-selector_%28condensed%29 for options
121
+ # <em> Note: </em> I don't think the block is used here.
122
+ def fb_multi_friend_selector_condensed(options={},&block)
123
+ options = options.dup
124
+ tag("fb:multi-friend-selector",stringify_vals(options.merge(:condensed=>true)))
125
+ end
126
+
127
+ # Render a button in a request using the <fb:req-choice> tag
128
+ # url must be an absolute url
129
+ # This should be used inside the block of an fb_multi_friend_request
130
+ def fb_req_choice(label,url)
131
+ tag "fb:req-choice",:label=>label,:url=>url
132
+ end
133
+
134
+ # Create a facebook form using <fb:editor>
135
+ #
136
+ # It yields a form builder that will convert the standard rails form helpers
137
+ # into the facebook specific version.
138
+ #
139
+ # Example:
140
+ # <% facebook_form_for(:poke,@poke,:url => create_poke_path) do |f| %>
141
+ # <%= f.text_field :message, :label=>"message" %>
142
+ # <%= f.buttons "Save Poke" %>
143
+ # <% end %>
144
+ #
145
+ # will generate
146
+ #
147
+ # <fb:editor action="/pokes/create">
148
+ # <fb:editor-text name="poke[message]" id="poke_message" value="" label="message" />
149
+ # <fb:editor-buttonset>
150
+ # <fb:editor-button label="Save Poke"
151
+ # </fb:editor-buttonset>
152
+ # </fb:editor>
153
+ def facebook_form_for( record_or_name_or_array,*args, &proc)
154
+
155
+ raise ArgumentError, "Missing block" unless block_given?
156
+ options = args.last.is_a?(Hash) ? args.pop : {}
157
+
158
+ case record_or_name_or_array
159
+ when String, Symbol
160
+ object_name = record_or_name_or_array
161
+ when Array
162
+ object = record_or_name_or_array.last
163
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
164
+ apply_form_for_options!(record_or_name_or_array, options)
165
+ args.unshift object
166
+ else
167
+ object = record_or_name_or_array
168
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
169
+ apply_form_for_options!([object], options)
170
+ args.unshift object
171
+ end
172
+ method = (options[:html]||{})[:method]
173
+ options[:builder] ||= Facebooker::Rails::FacebookFormBuilder
174
+ editor_options={}
175
+
176
+ action=options.delete(:url)
177
+ editor_options[:action]= action unless action.blank?
178
+ width=options.delete(:width)
179
+ editor_options[:width]=width unless width.blank?
180
+ width=options.delete(:labelwidth)
181
+ editor_options[:labelwidth]=width unless width.blank?
182
+
183
+ concat(tag("fb:editor",editor_options,true) , proc.binding)
184
+ concat(tag(:input,{:type=>"hidden",:name=>:_method, :value=>method},false), proc.binding) unless method.blank?
185
+ concat(token_tag, proc.binding)
186
+ fields_for( object_name,*(args << options), &proc)
187
+ concat("</fb:editor>",proc.binding)
188
+ end
189
+
190
+ # Render an fb:name tag for the given user
191
+ # This renders the name of the user specified. You can use this tag as both subject and object of
192
+ # a sentence. <em> See </em> http://wiki.developers.facebook.com/index.php/Fb:name for full description.
193
+ # Use this tag on FBML pages instead of retrieving the user's info and rendering the name explicitly.
194
+ #
195
+ def fb_name(user, options={})
196
+ options = options.dup
197
+ options.transform_keys!(FB_NAME_OPTION_KEYS_TO_TRANSFORM)
198
+ options.assert_valid_keys(FB_NAME_VALID_OPTION_KEYS)
199
+ options.merge!(:uid => cast_to_facebook_id(user))
200
+ content_tag("fb:name",nil, stringify_vals(options))
201
+ end
202
+
203
+ FB_NAME_OPTION_KEYS_TO_TRANSFORM = {:first_name_only => :firstnameonly,
204
+ :last_name_only => :lastnameonly,
205
+ :show_network => :shownetwork,
206
+ :use_you => :useyou,
207
+ :if_cant_see => :ifcantsee,
208
+ :subject_id => :subjectid}
209
+ FB_NAME_VALID_OPTION_KEYS = [:firstnameonly, :linked, :lastnameonly, :possessive, :reflexive,
210
+ :shownetwork, :useyou, :ifcantsee, :capitalize, :subjectid]
211
+
212
+ # Render an <fb:pronoun> tag for the specified user
213
+ # Options give flexibility for placing in any part of a sentence.
214
+ # <em> See </em> http://wiki.developers.facebook.com/index.php/Fb:pronoun for complete list of options.
215
+ #
216
+ def fb_pronoun(user, options={})
217
+ options = options.dup
218
+ options.transform_keys!(FB_PRONOUN_OPTION_KEYS_TO_TRANSFORM)
219
+ options.assert_valid_keys(FB_PRONOUN_VALID_OPTION_KEYS)
220
+ options.merge!(:uid => cast_to_facebook_id(user))
221
+ content_tag("fb:pronoun",nil, stringify_vals(options))
222
+ end
223
+
224
+ FB_PRONOUN_OPTION_KEYS_TO_TRANSFORM = {:use_you => :useyou, :use_they => :usethey}
225
+ FB_PRONOUN_VALID_OPTION_KEYS = [:useyou, :possessive, :reflexive, :objective,
226
+ :usethey, :capitalize]
227
+
228
+ # Render an fb:ref tag.
229
+ # Options must contain either url or handle.
230
+ # * <em> url </em> The URL from which to fetch the FBML. You may need to call fbml.refreshRefUrl to refresh cache
231
+ # * <em> handle </em> The string previously set by fbml.setRefHandle that identifies the FBML
232
+ # <em> See </em> http://wiki.developers.facebook.com/index.php/Fb:ref for complete description
233
+ def fb_ref(options)
234
+ options.assert_valid_keys(FB_REF_VALID_OPTION_KEYS)
235
+ validate_fb_ref_has_either_url_or_handle(options)
236
+ validate_fb_ref_does_not_have_both_url_and_handle(options)
237
+ tag("fb:ref", stringify_vals(options))
238
+ end
239
+
240
+ def validate_fb_ref_has_either_url_or_handle(options)
241
+ unless options.has_key?(:url) || options.has_key?(:handle)
242
+ raise ArgumentError, "fb_ref requires :url or :handle"
243
+ end
244
+ end
245
+
246
+ def validate_fb_ref_does_not_have_both_url_and_handle(options)
247
+ if options.has_key?(:url) && options.has_key?(:handle)
248
+ raise ArgumentError, "fb_ref may not have both :url and :handle"
249
+ end
250
+ end
251
+
252
+ FB_REF_VALID_OPTION_KEYS = [:url, :handle]
253
+
254
+ # Render an <fb:profile-pic> for the specified user.
255
+ #
256
+ # You can optionally specify the size using the :size=> option.
257
+ #
258
+ # Valid sizes are :thumb, :small, :normal and :square
259
+ def fb_profile_pic(user, options={})
260
+ options = options.dup
261
+ validate_fb_profile_pic_size(options)
262
+ options.merge!(:uid => cast_to_facebook_id(user))
263
+ content_tag("fb:profile-pic", nil,stringify_vals(options))
264
+ end
265
+
266
+ # Render an fb:photo tag.
267
+ # photo is either a Facebooker::Photo or an id of a Facebook photo or an object that responds to photo_id.
268
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:photo for complete list of options.
269
+ def fb_photo(photo, options={})
270
+ options = options.dup
271
+ options.assert_valid_keys(FB_PHOTO_VALID_OPTION_KEYS)
272
+ options.merge!(:pid => cast_to_photo_id(photo))
273
+ validate_fb_photo_size(options)
274
+ validate_fb_photo_align_value(options)
275
+ content_tag("fb:photo",nil, stringify_vals(options))
276
+ end
277
+
278
+ FB_PHOTO_VALID_OPTION_KEYS = [:uid, :size, :align]
279
+
280
+ def cast_to_photo_id(object)
281
+ object.respond_to?(:photo_id) ? object.photo_id : object
282
+ end
283
+
284
+ VALID_FB_SHARED_PHOTO_SIZES = [:thumb, :small, :normal, :square]
285
+ VALID_FB_PHOTO_SIZES = VALID_FB_SHARED_PHOTO_SIZES
286
+ VALID_FB_PROFILE_PIC_SIZES = VALID_FB_SHARED_PHOTO_SIZES
287
+ VALID_PERMISSIONS=[:email, :offline_access, :status_update, :photo_upload, :create_listing, :create_event, :rsvp_event, :sms]
288
+
289
+ # Render an fb:tabs tag containing some number of fb:tab_item tags.
290
+ # Example:
291
+ # <% fb_tabs do %>
292
+ # <%= fb_tab_item("Home", "home") %>
293
+ # <%= fb_tab_item("Office", "office") %>
294
+ # <% end %>
295
+ def fb_tabs(&block)
296
+ content = capture(&block)
297
+ concat(content_tag("fb:tabs", content), block.binding)
298
+ end
299
+
300
+ # Render an fb:tab_item tag.
301
+ # Use this in conjunction with fb_tabs
302
+ # Options can contains :selected => true to indicate that a tab is the current tab.
303
+ # <em> See: </em> http://wiki.developers.facebook.com/index.php/Fb:tab-item for complete list of options
304
+ def fb_tab_item(title, url, options={})
305
+ options= options.dup
306
+ options.assert_valid_keys(FB_TAB_ITEM_VALID_OPTION_KEYS)
307
+ options.merge!(:title => title, :href => url)
308
+ validate_fb_tab_item_align_value(options)
309
+ tag("fb:tab-item", stringify_vals(options))
310
+ end
311
+
312
+ FB_TAB_ITEM_VALID_OPTION_KEYS = [:align, :selected]
313
+
314
+ def validate_fb_tab_item_align_value(options)
315
+ if options.has_key?(:align) && !VALID_FB_TAB_ITEM_ALIGN_VALUES.include?(options[:align].to_sym)
316
+ raise(ArgumentError, "Unkown value for align: #{options[:align]}")
317
+ end
318
+ end
319
+
320
+ def validate_fb_photo_align_value(options)
321
+ if options.has_key?(:align) && !VALID_FB_PHOTO_ALIGN_VALUES.include?(options[:align].to_sym)
322
+ raise(ArgumentError, "Unkown value for align: #{options[:align]}")
323
+ end
324
+ end
325
+
326
+ VALID_FB_SHARED_ALIGN_VALUES = [:left, :right]
327
+ VALID_FB_PHOTO_ALIGN_VALUES = VALID_FB_SHARED_ALIGN_VALUES
328
+ VALID_FB_TAB_ITEM_ALIGN_VALUES = VALID_FB_SHARED_ALIGN_VALUES
329
+
330
+
331
+ # Create a Facebook wall. It can contain fb_wall_posts
332
+ #
333
+ # For Example:
334
+ # <% fb_wall do %>
335
+ # <%= fb_wall_post(@user,"This is my message") %>
336
+ # <%= fb_wall_post(@otheruser,"This is another message") %>
337
+ # <% end %>
338
+ def fb_wall(&proc)
339
+ content = capture(&proc)
340
+ concat(content_tag("fb:wall",content,{}),proc.binding)
341
+ end
342
+
343
+ # Render an <fb:wallpost> tag
344
+ # TODO: Optionally takes a time parameter t = int The current time, which is displayed in epoch seconds.
345
+ def fb_wallpost(user,message)
346
+ content_tag("fb:wallpost",message,:uid=>cast_to_facebook_id(user))
347
+ end
348
+ alias_method :fb_wall_post, :fb_wallpost
349
+
350
+ # Render an <fb:error> tag
351
+ # If message and text are present then this will render fb:error and fb:message tag
352
+ # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten'
353
+ def fb_error(message, text=nil)
354
+ fb_status_msg("error", message, text)
355
+ end
356
+
357
+ # Render an <fb:explanation> tag
358
+ # If message and text are present then this will render fb:error and fb:message tag
359
+ # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten'
360
+ def fb_explanation(message, text=nil)
361
+ fb_status_msg("explanation", message, text)
362
+ end
363
+
364
+ # Render an <fb:success> tag
365
+ # If message and text are present then this will render fb:error and fb:message tag
366
+ # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten'
367
+ def fb_success(message, text=nil)
368
+ fb_status_msg("success", message, text)
369
+ end
370
+
371
+ # Render flash values as <fb:message> and <fb:error> tags
372
+ #
373
+ # values in flash[:notice] will be rendered as an <fb:message>
374
+ #
375
+ # values in flash[:error] will be rendered as an <fb:error>
376
+ # TODO: Allow flash[:info] to render fb_explanation
377
+ def facebook_messages
378
+ message=""
379
+ unless flash[:notice].blank?
380
+ message += fb_success(flash[:notice])
381
+ end
382
+ unless flash[:error].blank?
383
+ message += fb_error(flash[:error])
384
+ end
385
+ message
386
+ end
387
+
388
+ # Create a dashboard. It can contain fb_action, fb_help, and fb_create_button
389
+ #
390
+ # For Example:
391
+ # <% fb_dashboard do %>
392
+ # <%= APP_NAME %>
393
+ # <%= fb_action 'My Matches', search_path %>
394
+ # <%= fb_help 'Feedback', "http://www.facebook.com/apps/application.php?id=6236036681" %>
395
+ # <%= fb_create_button 'Invite Friends', main_path %>
396
+ # <% end %>
397
+ def fb_dashboard(&proc)
398
+ if block_given?
399
+ content = capture(&proc)
400
+ concat(content_tag("fb:dashboard",content,{}),proc.binding)
401
+ else
402
+ content_tag("fb:dashboard",content,{})
403
+ end
404
+ end
405
+
406
+ # Content for the wide profile box goes in this tag
407
+ def fb_wide(&proc)
408
+ content = capture(&proc)
409
+ concat(content_tag("fb:wide", content, {}), proc.binding)
410
+ end
411
+
412
+ # Content for the narrow profile box goes in this tag
413
+ def fb_narrow(&proc)
414
+ content = capture(&proc)
415
+ concat(content_tag("fb:narrow", content, {}), proc.binding)
416
+ end
417
+
418
+ # Renders an action using the <fb:action> tag
419
+ def fb_action(name, url)
420
+ "<fb:action href=\"#{url_for(url)}\">#{name}</fb:action>"
421
+ end
422
+
423
+ # Render a <fb:help> tag
424
+ # For use inside <fb:dashboard>
425
+ def fb_help(name, url)
426
+ "<fb:help href=\"#{url_for(url)}\">#{name}</fb:help>"
427
+ end
428
+
429
+ # Render a <fb:create-button> tag
430
+ # For use inside <fb:dashboard>
431
+ def fb_create_button(name, url)
432
+ "<fb:create-button href=\"#{url_for(url)}\">#{name}</fb:create-button>"
433
+ end
434
+
435
+ # Create a comment area
436
+ # All the data for this content area is stored on the facebook servers.
437
+ # <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:comments for full details
438
+ def fb_comments(xid,canpost=true,candelete=false,numposts=5,options={})
439
+ options = options.dup
440
+ title = (title = options.delete(:title)) ? fb_title(title) : nil
441
+ content_tag "fb:comments",title,stringify_vals(options.merge(:xid=>xid,:canpost=>canpost.to_s,:candelete=>candelete.to_s,:numposts=>numposts))
442
+ end
443
+
444
+ # Adds a title to the title bar
445
+ #
446
+ # Facebook | App Name | This is the canvas page window title
447
+ #
448
+ # +title+: This is the canvas page window
449
+ def fb_title(title)
450
+ "<fb:title>#{title}</fb:title>"
451
+ end
452
+
453
+ # Create a Google Analytics tag
454
+ #
455
+ # +uacct+: Your Urchin/Google Analytics account ID.
456
+ def fb_google_analytics(uacct, options={})
457
+ options = options.dup
458
+ tag "fb:google-analytics", stringify_vals(options.merge(:uacct => uacct))
459
+ end
460
+
461
+ # Render if-is-app-user tag
462
+ # This tag renders the enclosing content only if the user specified has accepted the terms of service for the application.
463
+ # Use fb_if_user_has_added_app to determine wether the user has added the app.
464
+ # Example:
465
+ # <% fb_if_is_app_user(@facebook_user) do %>
466
+ # Thanks for accepting our terms of service!
467
+ # <% fb_else do %>
468
+ # Hey you haven't agreed to our terms. <%= link_to("Please accept our terms of service.", :action => "terms_of_service") %>
469
+ # <% end %>
470
+ #<% end %>
471
+ def fb_if_is_app_user(user=nil,options={},&proc)
472
+ content = capture(&proc)
473
+ options = options.dup
474
+ options.merge!(:uid=>cast_to_facebook_id(user)) if user
475
+ concat(content_tag("fb:if-is-app-user",content,stringify_vals(options)),proc.binding)
476
+ end
477
+
478
+ # Render if-user-has-added-app tag
479
+ # This tag renders the enclosing content only if the user specified has installed the application
480
+ #
481
+ # Example:
482
+ # <% fb_if_user_has_added_app(@facebook_user) do %>
483
+ # Hey you are an app user!
484
+ # <% fb_else do %>
485
+ # Hey you aren't an app user. <%= link_to("Add App and see the other side.", :action => "added_app") %>
486
+ # <% end %>
487
+ #<% end %>
488
+ def fb_if_user_has_added_app(user,options={},&proc)
489
+ content = capture(&proc)
490
+ options = options.dup
491
+ concat(content_tag("fb:if-user-has-added-app",content,stringify_vals(options.merge(:uid=>cast_to_facebook_id(user)))),proc.binding)
492
+ end
493
+
494
+ # Render fb:if-is-user tag
495
+ # This tag only renders enclosing content if the user is one of those specified
496
+ # user can be a single user or an Array of users
497
+ # Example:
498
+ # <% fb_if_is_user(@check_user) do %>
499
+ # <%= fb_name(@facebook_user) %> are one of the users. <%= link_to("Check the other side", :action => "friend") %>
500
+ # <% fb_else do %>
501
+ # <%= fb_name(@facebook_user) %> are not one of the users <%= fb_name(@check_user) %>
502
+ # <%= link_to("Check the other side", :action => "you") %>
503
+ # <% end %>
504
+ # <% end %>
505
+ def fb_if_is_user(user,&proc)
506
+ content = capture(&proc)
507
+ user = [user] unless user.is_a? Array
508
+ user_list=user.map{|u| cast_to_facebook_id(u)}.join(",")
509
+ concat(content_tag("fb:if-is-user",content,{:uid=>user_list}),proc.binding)
510
+ end
511
+
512
+ # Render fb:else tag
513
+ # Must be used within if block such as fb_if_is_user or fb_if_is_app_user . See example in fb_if_is_app_user
514
+ def fb_else(&proc)
515
+ content = capture(&proc)
516
+ concat(content_tag("fb:else",content),proc.binding)
517
+ end
518
+
519
+ #
520
+ # Return the URL for the about page of the application
521
+ def fb_about_url
522
+ "http://#{Facebooker.www_server_base_url}/apps/application.php?api_key=#{Facebooker.api_key}"
523
+ end
524
+
525
+ #
526
+ # Embed a discussion board named xid on the current page
527
+ #
528
+ def fb_board(xid,options={})
529
+ options = options.dup
530
+ tag("fb:board",stringify_vals(options.merge(:xid=>xid)))
531
+ end
532
+
533
+ def fb_add_profile_section
534
+ tag "fb:add-section-button",:section=>"profile"
535
+ end
536
+
537
+ def fb_add_info_section
538
+ tag "fb:add-section-button",:section=>"info"
539
+ end
540
+
541
+ def fb_prompt_permission(permission,message,callback=nil)
542
+ raise(ArgumentError, "Unknown value for permission: #{permission}") unless VALID_PERMISSIONS.include?(permission.to_sym)
543
+ args={:perms=>permission}
544
+ args[:next_fbjs]=callback unless callback.nil?
545
+ content_tag("fb:prompt-permission",message,args)
546
+ end
547
+
548
+ def fb_eventlink(eid)
549
+ content_tag "fb:eventlink",nil,:eid=>eid
550
+ end
551
+
552
+ def fb_grouplink(gid)
553
+ content_tag "fb:grouplink",nil,:gid=>gid
554
+ end
555
+
556
+ def fb_user_status(user,linked=true)
557
+ content_tag "fb:user-status",nil,stringify_vals(:uid=>cast_to_facebook_id(user), :linked=>linked)
558
+ end
559
+
560
+ def fb_share_button(url)
561
+ content_tag "fb:share-button",nil,:class=>"url",:href=>url
562
+ end
563
+
564
+ def fb_serverfbml(options={},&proc)
565
+ inner = capture(&proc)
566
+ concat(content_tag("fb:serverfbml",inner,options),&proc.binding)
567
+ end
568
+
569
+ def fb_container(options={},&proc)
570
+ inner = capture(&proc)
571
+ concat(content_tag("fb:container",inner,options),&proc.binding)
572
+ end
573
+
574
+ protected
575
+
576
+ def cast_to_facebook_id(object)
577
+ Facebooker::User.cast_to_facebook_id(object)
578
+ end
579
+
580
+ def validate_fb_profile_pic_size(options)
581
+ if options.has_key?(:size) && !VALID_FB_PROFILE_PIC_SIZES.include?(options[:size].to_sym)
582
+ raise(ArgumentError, "Unkown value for size: #{options[:size]}")
583
+ end
584
+ end
585
+
586
+ def validate_fb_photo_size(options)
587
+ if options.has_key?(:size) && !VALID_FB_PHOTO_SIZES.include?(options[:size].to_sym)
588
+ raise(ArgumentError, "Unkown value for size: #{options[:size]}")
589
+ end
590
+ end
591
+
592
+ private
593
+ def stringify_vals(hash)
594
+ result={}
595
+ hash.each do |key,value|
596
+ result[key]=value.to_s
597
+ end
598
+ result
599
+ end
600
+
601
+ def fb_status_msg(type, message, text)
602
+ if text.blank?
603
+ tag("fb:#{type}", :message => message)
604
+ else
605
+ content_tag("fb:#{type}", content_tag("fb:message", message) + text)
606
+ end
607
+ end
608
+
609
+ def token_tag
610
+ unless protect_against_forgery?
611
+ ''
612
+ else
613
+ tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
614
+ end
615
+ end
616
+ end
617
+ end
618
+ end
619
+
620
+ class Hash
621
+ def transform_keys!(transformation_hash)
622
+ transformation_hash.each_pair{|key, value| transform_key!(key, value)}
623
+ end
624
+
625
+
626
+ def transform_key!(old_key, new_key)
627
+ swapkey!(new_key, old_key)
628
+ end
629
+
630
+ ### This method is lifted from Ruby Facets core
631
+ def swapkey!( newkey, oldkey )
632
+ self[newkey] = self.delete(oldkey) if self.has_key?(oldkey)
633
+ self
634
+ end
635
+
636
+ # We can allow css attributes.
637
+ FB_ALWAYS_VALID_OPTION_KEYS = [:class, :style]
638
+ def assert_valid_keys(*valid_keys)
639
+ unknown_keys = keys - [valid_keys + FB_ALWAYS_VALID_OPTION_KEYS].flatten
640
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
641
+ end
642
+ end