motiro 0.6.3

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 (309) hide show
  1. data/LICENSE +280 -0
  2. data/README +28 -0
  3. data/README.en +175 -0
  4. data/README.pt-br +175 -0
  5. data/Rakefile +10 -0
  6. data/app/controllers/account_controller.rb +62 -0
  7. data/app/controllers/application.rb +72 -0
  8. data/app/controllers/edition_controller.rb +13 -0
  9. data/app/controllers/javascript_controller.rb +21 -0
  10. data/app/controllers/report_controller.rb +79 -0
  11. data/app/controllers/root_controller.rb +7 -0
  12. data/app/controllers/wiki_controller.rb +102 -0
  13. data/app/core/cache_reporter.rb +53 -0
  14. data/app/core/cache_reporter_fetcher.rb +33 -0
  15. data/app/core/chief_editor.rb +69 -0
  16. data/app/core/reporter.rb +105 -0
  17. data/app/core/reporter_driver.rb +36 -0
  18. data/app/core/reporter_fetcher.rb +39 -0
  19. data/app/core/settings.rb +43 -0
  20. data/app/core/version.rb +1 -0
  21. data/app/core/wiki_page_not_found.rb +33 -0
  22. data/app/core/wiki_reporter.rb +83 -0
  23. data/app/helpers/account_helper.rb +2 -0
  24. data/app/helpers/application_helper.rb +68 -0
  25. data/app/helpers/default_page_provider.rb +28 -0
  26. data/app/helpers/report_helper.rb +2 -0
  27. data/app/helpers/root_helper.rb +2 -0
  28. data/app/helpers/wiki_helper.rb +3 -0
  29. data/app/models/change.rb +96 -0
  30. data/app/models/diff_table_builder.rb +285 -0
  31. data/app/models/event.rb +18 -0
  32. data/app/models/headline.rb +109 -0
  33. data/app/models/page.rb +114 -0
  34. data/app/models/page_sweeper.rb +26 -0
  35. data/app/models/user.rb +85 -0
  36. data/app/ports/chdir_runner.rb +36 -0
  37. data/app/ports/reporter_loader.rb +9 -0
  38. data/app/ports/runner.rb +64 -0
  39. data/app/reporters/darcs_connection.rb +54 -0
  40. data/app/reporters/darcs_reporter.rb +104 -0
  41. data/app/reporters/darcs_settings.rb +9 -0
  42. data/app/reporters/darcs_temp_repo.rb +40 -0
  43. data/app/reporters/events_reporter.rb +28 -0
  44. data/app/reporters/features_reporter.rb +24 -0
  45. data/app/reporters/subversion_reporter.rb +203 -0
  46. data/app/reporters/svn_connection.rb +62 -0
  47. data/app/reporters/svn_settings.rb +40 -0
  48. data/app/views/account/_authorization.rhtml +54 -0
  49. data/app/views/account/_availability.rhtml +5 -0
  50. data/app/views/account/availability.rhtml +1 -0
  51. data/app/views/account/login.rhtml +22 -0
  52. data/app/views/account/logout.rhtml +10 -0
  53. data/app/views/javascript/motiro.rjs +132 -0
  54. data/app/views/javascript/niftycube.rjs +300 -0
  55. data/app/views/layouts/_bottom.rhtml +5 -0
  56. data/app/views/layouts/_header.rhtml +7 -0
  57. data/app/views/layouts/_top.rhtml +25 -0
  58. data/app/views/layouts/application.rhtml +3 -0
  59. data/app/views/layouts/scaffold.rhtml +13 -0
  60. data/app/views/layouts/wiki_edit.rhtml +26 -0
  61. data/app/views/report/list.rhtml +32 -0
  62. data/app/views/report/older.rhtml +26 -0
  63. data/app/views/report/rss.rxml +29 -0
  64. data/app/views/report/show.rhtml +20 -0
  65. data/app/views/root/index.rhtml +33 -0
  66. data/app/views/wiki/_editbar.rhtml +26 -0
  67. data/app/views/wiki/_properties_edit.rhtml +5 -0
  68. data/app/views/wiki/_properties_show.rhtml +5 -0
  69. data/app/views/wiki/edit.rhtml +56 -0
  70. data/app/views/wiki/properties_edit.rhtml +1 -0
  71. data/app/views/wiki/show.rhtml +9 -0
  72. data/bin/motiro +44 -0
  73. data/config/boot.rb +19 -0
  74. data/config/database.yml +14 -0
  75. data/config/environment.rb +64 -0
  76. data/config/environments/development.rb +20 -0
  77. data/config/environments/production.rb +19 -0
  78. data/config/environments/test.rb +19 -0
  79. data/config/motiro.yml +43 -0
  80. data/config/routes.rb +79 -0
  81. data/db/migrate/005_globalize_migration.rb +11363 -0
  82. data/db/migrate/006_remove_headline_title.rb +14 -0
  83. data/db/migrate/007_stretch_rid.rb +11 -0
  84. data/db/migrate/008_add_page_editors.rb +12 -0
  85. data/db/migrate/009_add_page_original_author.rb +12 -0
  86. data/db/migrate/010_remove_empty_string_defaults_from_pages.rb +17 -0
  87. data/db/migrate/011_add_title_and_kind_to_pages.rb +13 -0
  88. data/db/migrate/012_page_modification_info.rb +13 -0
  89. data/db/migrate/013_nullify_initial_page_attributes.rb +21 -0
  90. data/db/migrate/014_events_are_wiki_pages.rb +13 -0
  91. data/db/migrate/015_migrate_previous_event_data.rb +23 -0
  92. data/db/migrate/1_initial_structure.rb +36 -0
  93. data/db/migrate/2_add_authentication.rb +12 -0
  94. data/db/migrate/3_drop_articles.rb +26 -0
  95. data/db/migrate/4_add_page_editing.rb +14 -0
  96. data/db/motirodb.sqlite.initial +0 -0
  97. data/db/schema_version +1 -0
  98. data/db/translation/pt-BR.rb +76 -0
  99. data/doc/README_FOR_APP +2 -0
  100. data/installer/rails_installer_defaults.yml +5 -0
  101. data/lib/import_translations.rb +154 -0
  102. data/lib/login_system.rb +89 -0
  103. data/lib/relative_time.rb +48 -0
  104. data/lib/string_extensions.rb +10 -0
  105. data/lib/stub_hash.rb +22 -0
  106. data/lib/tasks/packaging.rake +93 -0
  107. data/lib/tasks/testing.rake +32 -0
  108. data/lib/tick_daemon.rb +41 -0
  109. data/lib/translator.rb +67 -0
  110. data/lib/wiki_renderer.rb +42 -0
  111. data/lib/wiki_url_generator.rb +29 -0
  112. data/log/.keepdir +0 -0
  113. data/public/404.html +8 -0
  114. data/public/500.html +8 -0
  115. data/public/dispatch.cgi +10 -0
  116. data/public/dispatch.fcgi +24 -0
  117. data/public/dispatch.rb +10 -0
  118. data/public/favicon.ico +0 -0
  119. data/public/images/calendar.png +0 -0
  120. data/public/images/rss.png +0 -0
  121. data/public/javascripts/controls.js +750 -0
  122. data/public/javascripts/dragdrop.js +584 -0
  123. data/public/javascripts/effects.js +854 -0
  124. data/public/javascripts/prototype.js +1785 -0
  125. data/public/robots.txt +1 -0
  126. data/public/stylesheets/motiro.css +269 -0
  127. data/public/stylesheets/niftyCorners.css +35 -0
  128. data/public/stylesheets/scaffold.css +74 -0
  129. data/script/about +3 -0
  130. data/script/breakpointer +3 -0
  131. data/script/console +3 -0
  132. data/script/destroy +3 -0
  133. data/script/generate +3 -0
  134. data/script/performance/benchmarker +3 -0
  135. data/script/performance/profiler +3 -0
  136. data/script/plugin +3 -0
  137. data/script/process/reaper +3 -0
  138. data/script/process/spawner +3 -0
  139. data/script/process/spinner +3 -0
  140. data/script/runner +3 -0
  141. data/script/server +3 -0
  142. data/script/ticker +29 -0
  143. data/test/acceptance/account_test.rb +186 -0
  144. data/test/acceptance/darcs_test.rb +62 -0
  145. data/test/acceptance/events_test.rb +47 -0
  146. data/test/acceptance/main_page_test.rb +92 -0
  147. data/test/acceptance/subversion_test.rb +319 -0
  148. data/test/acceptance/ts_all_suites.rb +27 -0
  149. data/test/acceptance/wiki_test.rb +202 -0
  150. data/test/contract/darcs_test.rb +51 -0
  151. data/test/contract/remote_darcs_test.rb +61 -0
  152. data/test/contract/svn_test.rb +53 -0
  153. data/test/fixtures/changes.yml +25 -0
  154. data/test/fixtures/headlines.yml +45 -0
  155. data/test/fixtures/pages.yml +98 -0
  156. data/test/fixtures/users.yml +31 -0
  157. data/test/functional/account_controller_test.rb +96 -0
  158. data/test/functional/report_controller_test.rb +113 -0
  159. data/test/functional/report_features_test.rb +38 -0
  160. data/test/functional/report_subversion_test.rb +79 -0
  161. data/test/functional/root_controller_test.rb +127 -0
  162. data/test/functional/wiki_controller_test.rb +280 -0
  163. data/test/lib/acceptance_test_case.rb +43 -0
  164. data/test/lib/configuration.rb +53 -0
  165. data/test/lib/darcs_excerpts.rb +181 -0
  166. data/test/lib/darcs_repo.rb +77 -0
  167. data/test/lib/live_mode_test.rb +51 -0
  168. data/test/lib/local_svn.rb +157 -0
  169. data/test/lib/netutils.rb +42 -0
  170. data/test/lib/platform_thread.rb +62 -0
  171. data/test/lib/repoutils.rb +23 -0
  172. data/test/lib/selenium_extensions.rb +32 -0
  173. data/test/lib/stubio.rb +37 -0
  174. data/test/lib/svn_excerpts.rb +288 -0
  175. data/test/lib/test_configuration.rb +14 -0
  176. data/test/lib/webserver.rb +71 -0
  177. data/test/meta/configuration_test.rb +72 -0
  178. data/test/meta/darcs_repo_test.rb +118 -0
  179. data/test/meta/local_svn_test.rb +125 -0
  180. data/test/meta/platform_thread_test.rb +46 -0
  181. data/test/meta/stubio_test.rb +44 -0
  182. data/test/mocks/headline.rb +34 -0
  183. data/test/mocks/svn_reporter.rb +29 -0
  184. data/test/stubs/svn_settings.rb +19 -0
  185. data/test/stubs/url_generator.rb +24 -0
  186. data/test/test_helper.rb +36 -0
  187. data/test/unit/cache_reporter_fetcher_test.rb +46 -0
  188. data/test/unit/cache_reporter_test.rb +97 -0
  189. data/test/unit/caching_test.rb +78 -0
  190. data/test/unit/change_test.rb +152 -0
  191. data/test/unit/chdir_runner_test.rb +77 -0
  192. data/test/unit/chief_editor_test.rb +234 -0
  193. data/test/unit/darcs_connection_test.rb +109 -0
  194. data/test/unit/darcs_reporter_test.rb +146 -0
  195. data/test/unit/darcs_settings_test.rb +37 -0
  196. data/test/unit/darcs_temp_repo_test.rb +51 -0
  197. data/test/unit/default_page_provider_test.rb +46 -0
  198. data/test/unit/diff_table_builder_test.rb +602 -0
  199. data/test/unit/headline_test.rb +259 -0
  200. data/test/unit/page_test.rb +145 -0
  201. data/test/unit/relative_time_test.rb +56 -0
  202. data/test/unit/reporter_driver_test.rb +85 -0
  203. data/test/unit/reporter_fetcher_test.rb +31 -0
  204. data/test/unit/reporter_test.rb +81 -0
  205. data/test/unit/runner_test.rb +93 -0
  206. data/test/unit/settings_test.rb +55 -0
  207. data/test/unit/string_extensions_test.rb +10 -0
  208. data/test/unit/svn_connection_test.rb +183 -0
  209. data/test/unit/svn_reporter_interaction_test.rb +38 -0
  210. data/test/unit/svn_reporter_test.rb +286 -0
  211. data/test/unit/svn_settings_test.rb +86 -0
  212. data/test/unit/translator_test.rb +96 -0
  213. data/test/unit/user_test.rb +125 -0
  214. data/test/unit/wiki_renderer_test.rb +87 -0
  215. data/test/unit/wiki_reporter_test.rb +94 -0
  216. data/test/unit/wiki_url_generator_test.rb +31 -0
  217. data/vendor/motiro-installer.rb +159 -0
  218. data/vendor/plugins/globalize/LICENSE +9 -0
  219. data/vendor/plugins/globalize/README +49 -0
  220. data/vendor/plugins/globalize/data/country_data.csv +240 -0
  221. data/vendor/plugins/globalize/data/language_data.csv +188 -0
  222. data/vendor/plugins/globalize/data/translation_data.csv +3421 -0
  223. data/vendor/plugins/globalize/generators/globalize/USAGE +10 -0
  224. data/vendor/plugins/globalize/generators/globalize/globalize_generator.rb +42 -0
  225. data/vendor/plugins/globalize/generators/globalize/templates/migration.rb.gz +0 -0
  226. data/vendor/plugins/globalize/generators/globalize/templates/tiny_migration.rb.gz +0 -0
  227. data/vendor/plugins/globalize/init.rb +30 -0
  228. data/vendor/plugins/globalize/lib/globalize/localization/core_ext.rb +170 -0
  229. data/vendor/plugins/globalize/lib/globalize/localization/core_ext_hooks.rb +33 -0
  230. data/vendor/plugins/globalize/lib/globalize/localization/db_translate.rb +494 -0
  231. data/vendor/plugins/globalize/lib/globalize/localization/db_view_translator.rb +152 -0
  232. data/vendor/plugins/globalize/lib/globalize/localization/locale.rb +173 -0
  233. data/vendor/plugins/globalize/lib/globalize/localization/rfc_3066.rb +40 -0
  234. data/vendor/plugins/globalize/lib/globalize/models/country.rb +24 -0
  235. data/vendor/plugins/globalize/lib/globalize/models/currency.rb +188 -0
  236. data/vendor/plugins/globalize/lib/globalize/models/language.rb +84 -0
  237. data/vendor/plugins/globalize/lib/globalize/models/model_translation.rb +4 -0
  238. data/vendor/plugins/globalize/lib/globalize/models/translation.rb +9 -0
  239. data/vendor/plugins/globalize/lib/globalize/models/view_translation.rb +14 -0
  240. data/vendor/plugins/globalize/lib/globalize/rails/action_mailer.rb +125 -0
  241. data/vendor/plugins/globalize/lib/globalize/rails/action_view.rb +79 -0
  242. data/vendor/plugins/globalize/lib/globalize/rails/active_record.rb +129 -0
  243. data/vendor/plugins/globalize/lib/globalize/rails/active_record_helper.rb +33 -0
  244. data/vendor/plugins/globalize/populators/pop_dates.rb +81 -0
  245. data/vendor/plugins/globalize/populators/pop_migration.rb +18 -0
  246. data/vendor/plugins/globalize/populators/pop_pluralization.rb +26 -0
  247. data/vendor/plugins/globalize/populators/pop_seps.rb +32 -0
  248. data/vendor/plugins/globalize/tasks/data.rake +130 -0
  249. data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.en-US.plain.text.rhtml +1 -0
  250. data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.en.plain.text.rhtml +1 -0
  251. data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.he.plain.text.rhtml +1 -0
  252. data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.plain.text.rhtml +1 -0
  253. data/vendor/plugins/globalize/test/action_mailer_test.rb +54 -0
  254. data/vendor/plugins/globalize/test/config/database.yml.default +16 -0
  255. data/vendor/plugins/globalize/test/config/database.yml.example +22 -0
  256. data/vendor/plugins/globalize/test/core_ext_test.rb +61 -0
  257. data/vendor/plugins/globalize/test/currency_test.rb +141 -0
  258. data/vendor/plugins/globalize/test/date_helper_test.rb +634 -0
  259. data/vendor/plugins/globalize/test/db/schema.rb +90 -0
  260. data/vendor/plugins/globalize/test/db_translation_test.rb +374 -0
  261. data/vendor/plugins/globalize/test/fixtures/globalize_categories.yml +7 -0
  262. data/vendor/plugins/globalize/test/fixtures/globalize_categories_products.yml +7 -0
  263. data/vendor/plugins/globalize/test/fixtures/globalize_countries.yml +41 -0
  264. data/vendor/plugins/globalize/test/fixtures/globalize_languages.yml +64 -0
  265. data/vendor/plugins/globalize/test/fixtures/globalize_manufacturers.yml +5 -0
  266. data/vendor/plugins/globalize/test/fixtures/globalize_products.yml +29 -0
  267. data/vendor/plugins/globalize/test/fixtures/globalize_simples.yml +5 -0
  268. data/vendor/plugins/globalize/test/fixtures/globalize_translations.yml +354 -0
  269. data/vendor/plugins/globalize/test/locale_test.rb +27 -0
  270. data/vendor/plugins/globalize/test/mime_responds_test.rb +358 -0
  271. data/vendor/plugins/globalize/test/model_test.rb +17 -0
  272. data/vendor/plugins/globalize/test/standard_data_test_helper.rb +33 -0
  273. data/vendor/plugins/globalize/test/test_helper.rb +19 -0
  274. data/vendor/plugins/globalize/test/test_standard_data.rb +54 -0
  275. data/vendor/plugins/globalize/test/validation_test.rb +29 -0
  276. data/vendor/plugins/globalize/test/view_picking_test.rb +49 -0
  277. data/vendor/plugins/globalize/test/view_translation_test.rb +237 -0
  278. data/vendor/plugins/globalize/test/views/layouts/standard.rhtml +1 -0
  279. data/vendor/plugins/globalize/test/views/respond_to/all_types_with_layout.rhtml +1 -0
  280. data/vendor/plugins/globalize/test/views/respond_to/all_types_with_layout.rjs +1 -0
  281. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.en.rhtml +1 -0
  282. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.en.rjs +1 -0
  283. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.en.rxml +1 -0
  284. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.fr.rhtml +1 -0
  285. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.fr.rjs +1 -0
  286. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.fr.rxml +1 -0
  287. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.rhtml +1 -0
  288. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.rjs +1 -0
  289. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.rxml +1 -0
  290. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.en.rhtml +1 -0
  291. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.en.rjs +1 -0
  292. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.en.rxml +1 -0
  293. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.fr.rhtml +1 -0
  294. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.fr.rjs +1 -0
  295. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.fr.rxml +1 -0
  296. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.rhtml +1 -0
  297. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.rjs +1 -0
  298. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.rxml +1 -0
  299. data/vendor/plugins/globalize/test/views/test.he-IL.rhtml +1 -0
  300. data/vendor/plugins/globalize/test/views/test.rhtml +1 -0
  301. data/vendor/plugins/globalize/test/views/test2.he.rhtml +1 -0
  302. data/vendor/plugins/globalize/test/views/test2.rhtml +1 -0
  303. data/vendor/plugins/test_xml/MIT-LICENSE +20 -0
  304. data/vendor/plugins/test_xml/README +20 -0
  305. data/vendor/plugins/test_xml/Rakefile +22 -0
  306. data/vendor/plugins/test_xml/init.rb +7 -0
  307. data/vendor/plugins/test_xml/lib/xml_assertions.rb +22 -0
  308. data/vendor/plugins/test_xml/test/test_xml_test.rb +40 -0
  309. metadata +505 -0
@@ -0,0 +1,494 @@
1
+ module Globalize # :nodoc:
2
+
3
+ class WrongLanguageError < ActiveRecord::ActiveRecordError
4
+ attr_reader :original_language, :active_language
5
+ def initialize(orig_lang, active_lang)
6
+ @original_language = orig_lang
7
+ @active_language = active_lang
8
+ end
9
+ end
10
+
11
+ class TranslationTrampleError < ActiveRecord::ActiveRecordError; end
12
+
13
+ module DbTranslate # :nodoc:
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ module ClassMethods
20
+ =begin rdoc
21
+ Specifies fields that can be translated. These are normal ActiveRecord
22
+ fields, with corresponding database columns, but they are shadowed
23
+ by translations in a special translation table. All the translation
24
+ stuff is done behind the scenes.
25
+
26
+ === Example:
27
+
28
+ ==== In your model:
29
+ class Product < ActiveRecord::Base
30
+ translates :name, :description
31
+ end
32
+
33
+ ==== In your controller:
34
+ Locale.set("en_US")
35
+ product.name -> guitar
36
+
37
+ Locale.set("es_ES")
38
+ product.name -> guitarra
39
+
40
+ The standard ActiveRecord +find+ method has been tweaked to work with Globalize.
41
+ Use it in the exact same way you would the regular find, except for the
42
+ following provisos:
43
+
44
+ 1. At this point, it will not work with the <tt>:include</tt> option...
45
+ 1. However, there is a replacement: <tt>:include_translated</tt>, which will
46
+ be described below.
47
+ 1. The <tt>:select</tt> option is prohibited.
48
+
49
+ +find+ returns the retreived models, with all translated fields correctly
50
+ loaded, depending on the active language.
51
+
52
+ <tt>:include_translated</tt> works as follows:
53
+ any model specified in the <tt>:include_translated</tt> option
54
+ will be eagerly loaded and added to the current model as attributes,
55
+ prefixed with the name of the associated model. This is often referred
56
+ to as _piggybacking_.
57
+
58
+ Example:
59
+ class Product < ActiveRecord::Base
60
+ belongs_to :manufacturer
61
+ belongs_to :category
62
+ end
63
+
64
+ class Category < ActiveRecord::Base
65
+ has_many :products
66
+ translates :name
67
+ end
68
+
69
+ prods = Product.find(:all, :include_translated => [ :manufacturer, :category ])
70
+ prods.first.category_name -> "batedeira"
71
+ =end
72
+ def translates(*facets)
73
+ # parse out options hash
74
+ options = facets.pop if facets.last.kind_of? Hash
75
+ options ||= {}
76
+
77
+ facets_string = "[" + facets.map {|facet| ":#{facet}"}.join(", ") + "]"
78
+ class_eval <<-HERE
79
+ @@facet_options = {}
80
+ attr_writer :fully_loaded
81
+ def fully_loaded?; @fully_loaded; end
82
+ @@globalize_facets = #{facets_string}
83
+ @@preload_facets ||= @@globalize_facets
84
+ class << self
85
+
86
+ def sqlite?; connection.kind_of? ActiveRecord::ConnectionAdapters::SQLiteAdapter end
87
+
88
+ def globalize_facets
89
+ @@globalize_facets
90
+ end
91
+
92
+ def globalize_facets_hash
93
+ @@globalize_facets_hash ||= globalize_facets.inject({}) {|hash, facet|
94
+ hash[facet.to_s] = true; hash
95
+ }
96
+ end
97
+
98
+ def untranslated_fields
99
+ @@untranslated_fields ||=
100
+ column_names.map {|cn| cn.intern } - globalize_facets
101
+ end
102
+
103
+ def preload_facets; @@preload_facets; end
104
+ def postload_facets
105
+ @@postload_facets ||= @@globalize_facets - @@preload_facets
106
+ end
107
+ alias_method :globalize_old_find_every, :find_every unless
108
+ respond_to? :globalize_old_find_every
109
+ end
110
+ alias_method :globalize_old_reload, :reload
111
+ alias_method :globalize_old_destroy, :destroy
112
+ alias_method :globalize_old_create_or_update, :create_or_update
113
+ alias_method :globalize_old_update, :update
114
+
115
+ include Globalize::DbTranslate::TranslateObjectMethods
116
+ extend Globalize::DbTranslate::TranslateClassMethods
117
+
118
+ HERE
119
+
120
+ facets.each do |facet|
121
+ bidi = (!(options[facet] && !options[facet][:bidi_embed])).to_s
122
+ class_eval <<-HERE
123
+ @@facet_options[:#{facet}] ||= {}
124
+ @@facet_options[:#{facet}][:bidi] = #{bidi}
125
+
126
+ def #{facet}
127
+ if not_original_language
128
+ raise WrongLanguageError.new(@original_language, Locale.language)
129
+ end
130
+ load_other_translations if
131
+ !fully_loaded? && !self.class.preload_facets.include?(:#{facet})
132
+ result = read_attribute(:#{facet})
133
+ return nil if result.nil?
134
+ result.direction = #{facet}_is_base? ?
135
+ (Locale.base_language ? Locale.base_language.direction : nil) :
136
+ (@original_language ? @original_language.direction : nil)
137
+
138
+ # insert bidi embedding characters, if necessary
139
+ if @@facet_options[:#{facet}][:bidi] &&
140
+ Locale.language && Locale.language.direction && result.direction
141
+ if Locale.language.direction == 'ltr' && result.direction == 'rtl'
142
+ bidi_str = "\xe2\x80\xab" + result + "\xe2\x80\xac"
143
+ bidi_str.direction = result.direction
144
+ return bidi_str
145
+ elsif Locale.language.direction == 'rtl' && result.direction == 'ltr'
146
+ bidi_str = "\xe2\x80\xaa" + result + "\xe2\x80\xac"
147
+ bidi_str.direction = result.direction
148
+ return bidi_str
149
+ end
150
+ end
151
+
152
+ return result
153
+ end
154
+
155
+ def #{facet}=(arg)
156
+ raise WrongLanguageError.new(@original_language, Locale.language) if
157
+ not_original_language
158
+ write_attribute(:#{facet}, arg)
159
+ end
160
+
161
+ def #{facet}_is_base?
162
+ self['#{facet}_not_base'].nil?
163
+ end
164
+ HERE
165
+ end
166
+
167
+ end
168
+
169
+ =begin rdoc
170
+ Optionally specifies translated fields to be preloaded on <tt>find</tt>. For instance,
171
+ in a product catalog, you may want to do a <tt>find</tt> of the first 10 products:
172
+
173
+ Product.find(:all, :limit => 10, :order => "name")
174
+
175
+ But you wouldn't want to load the complete descriptions and specs of all the
176
+ products, just the names and summaries. So you'd specify:
177
+
178
+ class Product < ActiveRecord::Base
179
+ translates :name, :summary, :description, :specs
180
+ translates_preload :name, :summary
181
+ ...
182
+ end
183
+
184
+ By default (if no translates_preload is specified), Globalize will preload
185
+ the first field given to <tt>translates</tt>. It will also fully load on
186
+ a <tt>find(:first)</tt> or when <tt>:translate_all => true</tt> is given as a find option.
187
+ =end
188
+ def translates_preload(*facets)
189
+ module_eval <<-HERE
190
+ @@preload_facets = facets
191
+ HERE
192
+ end
193
+
194
+ end
195
+
196
+ module TranslateObjectMethods # :nodoc: all
197
+
198
+ module_eval <<-HERE
199
+ def not_original_language
200
+ return false if @original_language.nil?
201
+ return @original_language != Locale.language
202
+ end
203
+
204
+ def set_original_language
205
+ @original_language = Locale.language
206
+ end
207
+ HERE
208
+
209
+ def load_other_translations
210
+ postload_facets = self.class.postload_facets
211
+ return if postload_facets.empty? || new_record?
212
+
213
+ table_name = self.class.table_name
214
+ facet_selection = postload_facets.join(", ")
215
+ base = connection.select_one("SELECT #{facet_selection} " +
216
+ " FROM #{table_name} WHERE #{self.class.primary_key} = #{id}",
217
+ "loading base for load_other_translations")
218
+ base.each {|key, val| write_attribute( key, val ) }
219
+
220
+ unless Locale.base?
221
+ trs = ModelTranslation.find(:all,
222
+ :conditions => [ "table_name = ? AND item_id = ? AND language_id = ? AND " +
223
+ "facet IN (#{[ '?' ] * postload_facets.size * ', '})", table_name,
224
+ self.id, Locale.active.language.id ] + postload_facets.map {|facet| facet.to_s} )
225
+ trs ||= []
226
+ trs.each do |tr|
227
+ attr = tr.text || base[tr.facet.to_s]
228
+ write_attribute( tr.facet, attr )
229
+ end
230
+ end
231
+ self.fully_loaded = true
232
+ end
233
+
234
+ def destroy
235
+ globalize_old_destroy
236
+ ModelTranslation.delete_all( [ "table_name = ? AND item_id = ?",
237
+ self.class.table_name, id ])
238
+ end
239
+
240
+ def reload
241
+ globalize_old_reload
242
+ set_original_language
243
+ end
244
+
245
+ private
246
+
247
+ # Returns copy of the attributes hash where all the values have been safely quoted for use in
248
+ # an SQL statement.
249
+ # REDEFINED to include only untranslated fields. We don't want to overwrite the
250
+ # base translation with other translations.
251
+ def attributes_with_quotes(include_primary_key = true)
252
+ if Locale.base?
253
+ attributes.inject({}) do |quoted, (name, value)|
254
+ if column = column_for_attribute(name)
255
+ quoted[name] = quote(value, column) unless !include_primary_key && column.primary
256
+ end
257
+ quoted
258
+ end
259
+ else
260
+ attributes.inject({}) do |quoted, (name, value)|
261
+ if !self.class.globalize_facets_hash.has_key?(name) &&
262
+ column = column_for_attribute(name)
263
+ quoted[name] = quote(value, column) unless !include_primary_key && column.primary
264
+ end
265
+ quoted
266
+ end
267
+ end
268
+ end
269
+
270
+ def create_or_update
271
+ result = globalize_old_create_or_update
272
+ update_translation if Locale.active && result
273
+ result
274
+ end
275
+
276
+ def update
277
+ status = true
278
+ status = globalize_old_update unless attributes_with_quotes(false).empty?
279
+ status
280
+ end
281
+
282
+ def update_translation
283
+ raise WrongLanguageError.new(@original_language, Locale.language) if
284
+ not_original_language
285
+
286
+ set_original_language
287
+
288
+ # nothing to do, facets updated in main model
289
+ return if Locale.base?
290
+
291
+ table_name = self.class.table_name
292
+ self.class.globalize_facets.each do |facet|
293
+ next unless has_attribute?(facet)
294
+ text = read_attribute(facet)
295
+ language_id = Locale.active.language.id
296
+ tr = ModelTranslation.find(:first, :conditions =>
297
+ [ "table_name = ? AND item_id = ? AND facet = ? AND language_id = ?",
298
+ table_name, id, facet.to_s, language_id ])
299
+ if tr.nil?
300
+ # create new record
301
+ ModelTranslation.create(:table_name => table_name,
302
+ :item_id => id, :facet => facet.to_s,
303
+ :language_id => language_id,
304
+ :text => text) unless text.nil?
305
+ elsif text.blank?
306
+ # delete record
307
+ tr.destroy
308
+ else
309
+ # update record
310
+ tr.update_attribute(:text, text) if tr.text != text
311
+ end
312
+ end # end facets loop
313
+ end
314
+
315
+ end
316
+
317
+ module TranslateClassMethods
318
+
319
+ # Use this instead of +find+ if you want to bypass the translation
320
+ # code for any reason.
321
+ def untranslated_find(*args)
322
+ has_options = args.last.is_a?(Hash)
323
+ options = has_options ? args.last : {}
324
+ options[:untranslated] = true
325
+ args << options if !has_options
326
+ find(*args)
327
+ end
328
+
329
+ protected
330
+ # FIX: figure out how to use default rails VALID_FIND_OPTIONS constant
331
+ VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
332
+ :order, :select, :readonly, :group, :from,
333
+ :untranslated, :include_translated ]
334
+
335
+ def validate_find_options(options) #:nodoc:
336
+ options.assert_valid_keys(VALID_FIND_OPTIONS)
337
+ end
338
+
339
+ private
340
+ def find_every(options)
341
+ return globalize_old_find_every(options) if options[:untranslated]
342
+ raise StandardError,
343
+ ":select option not allowed on translatable models " +
344
+ "(#{options[:select]})" if options[:select] && !options[:select].empty?
345
+
346
+ # do quick version if base language is active
347
+ if Locale.base? && !options.has_key?(:include_translated)
348
+ results = globalize_old_find_every(options)
349
+ results.each {|result|
350
+ result.set_original_language
351
+ }
352
+ return results
353
+ end
354
+
355
+ options[:conditions] = fix_conditions(options[:conditions]) if options[:conditions]
356
+
357
+ # there will at least be an +id+ field here
358
+ select_clause = untranslated_fields.map {|f| "#{table_name}.#{f}" }.join(", ")
359
+
360
+ joins_clause = options[:joins].nil? ? "" : options[:joins].dup
361
+ joins_args = []
362
+ load_full = options[:translate_all]
363
+ facets = load_full ? globalize_facets : preload_facets
364
+
365
+ if Locale.base?
366
+ select_clause << ', ' << facets.map {|f| "#{table_name}.#{f}" }.join(", ")
367
+ else
368
+ language_id = Locale.active.language.id
369
+ load_full = options[:translate_all]
370
+ facets = load_full ? globalize_facets : preload_facets
371
+
372
+ =begin
373
+ There's a bug in sqlite that messes up sorting when aliasing fields,
374
+ see: <http://www.sqlite.org/cvstrac/tktview?tn=1521,33>.
375
+
376
+ Since I want to use sqlite, and sorting, I'm hacking this to make it work.
377
+ This involves renaming order by fields and adding them to the SELECT part.
378
+ It's a sucky hack, but hopefully sqlite will fix the bug soon.
379
+ =end
380
+
381
+ # sqlite bug hack
382
+ select_position = untranslated_fields.size
383
+
384
+ # initialize where tweaking
385
+ if options[:conditions].nil?
386
+ where_clause = ""
387
+ else
388
+ if options[:conditions].kind_of? Array
389
+ conditions_is_array = true
390
+ where_clause = options[:conditions].shift
391
+ else
392
+ where_clause = options[:conditions]
393
+ end
394
+ end
395
+
396
+ facets.each do |facet|
397
+ facet = facet.to_s
398
+ facet_table_alias = "t_#{facet}"
399
+
400
+ # sqlite bug hack
401
+ select_position += 1
402
+ options[:order].sub!(/\b#{facet}\b/, select_position.to_s) if options[:order] && sqlite?
403
+
404
+ select_clause << ", COALESCE(#{facet_table_alias}.text, #{table_name}.#{facet}) AS #{facet}, "
405
+ select_clause << " #{facet_table_alias}.text AS #{facet}_not_base "
406
+ joins_clause << " LEFT OUTER JOIN globalize_translations AS #{facet_table_alias} " +
407
+ "ON #{facet_table_alias}.table_name = ? " +
408
+ "AND #{table_name}.#{primary_key} = #{facet_table_alias}.item_id " +
409
+ "AND #{facet_table_alias}.facet = ? AND #{facet_table_alias}.language_id = ? "
410
+ joins_args << table_name << facet << language_id
411
+
412
+ #for translated fields inside WHERE clause substitute corresponding COALESCE string
413
+ where_clause.gsub!(/((((#{table_name}\.)|\W)#{facet})|^#{facet})\W/, " COALESCE(#{facet_table_alias}.text, #{table_name}.#{facet}) ")
414
+ end
415
+
416
+ options[:conditions] = sanitize_sql(
417
+ conditions_is_array ? [ where_clause ] + options[:conditions] : where_clause
418
+ ) unless options[:conditions].nil?
419
+ end
420
+
421
+ # add in associations (of :belongs_to nature) if applicable
422
+ associations = options[:include_translated] || []
423
+ associations = [ associations ].flatten
424
+ associations.each do |assoc|
425
+ rfxn = reflect_on_association(assoc)
426
+ assoc_type = rfxn.macro
427
+ raise StandardError,
428
+ ":include_translated associations must be of type :belongs_to;" +
429
+ "#{assoc} is #{assoc_type}" if assoc_type != :belongs_to
430
+ klass = rfxn.klass
431
+ assoc_facets = klass.preload_facets
432
+ included_table = klass.table_name
433
+ included_fk = klass.primary_key
434
+ fk = rfxn.options[:foreign_key] || "#{assoc}_id"
435
+ assoc_facets.each do |facet|
436
+ facet_table_alias = "t_#{assoc}_#{facet}"
437
+
438
+ if Locale.base?
439
+ select_clause << ", #{included_table}.#{facet} AS #{assoc}_#{facet} "
440
+ else
441
+ select_clause << ", COALESCE(#{facet_table_alias}.text, #{included_table}.#{facet}) " +
442
+ "AS #{assoc}_#{facet} "
443
+ joins_clause << " LEFT OUTER JOIN globalize_translations AS #{facet_table_alias} " +
444
+ "ON #{facet_table_alias}.table_name = ? " +
445
+ "AND #{table_name}.#{fk} = #{facet_table_alias}.item_id " +
446
+ "AND #{facet_table_alias}.facet = ? AND #{facet_table_alias}.language_id = ? "
447
+ joins_args << klass.table_name << facet.to_s << language_id
448
+ end
449
+ end
450
+ joins_clause << "LEFT OUTER JOIN #{included_table} " +
451
+ "ON #{table_name}.#{fk} = #{included_table}.#{included_fk} "
452
+ end
453
+
454
+ options[:select] = select_clause
455
+ options[:readonly] = false
456
+
457
+ sanitized_joins_clause = sanitize_sql( [ joins_clause, *joins_args ] )
458
+ options[:joins] = sanitized_joins_clause
459
+ results = globalize_old_find_every(options)
460
+
461
+ results.each {|result|
462
+ result.set_original_language
463
+ result.fully_loaded = true if load_full
464
+ }
465
+
466
+ return results
467
+ end
468
+
469
+ # properly scope conditions to table
470
+ def fix_conditions(conditions)
471
+ if conditions.kind_of? Array
472
+ is_array = true
473
+ sql = conditions.shift
474
+ else
475
+ is_array = false
476
+ sql = conditions
477
+ end
478
+
479
+ column_names.each do |column_name|
480
+ sql.gsub!( /(^|([^\.\w"'`]+))(["'`]?)#{column_name}(?!\w)/,
481
+ '\1' + "#{table_name}." + '\3' + "#{column_name}" )
482
+ end
483
+
484
+ if is_array
485
+ [ sql ] + conditions
486
+ else
487
+ sql
488
+ end
489
+ end
490
+
491
+ end
492
+ end
493
+
494
+ end
@@ -0,0 +1,152 @@
1
+ module Globalize # :nodoc:
2
+ class DbViewTranslator
3
+ include Singleton
4
+
5
+ # The maximum size of the cache in kilobytes.
6
+ # This is just a rough estimate, the cache can grow bigger than this figure.
7
+ attr_accessor :max_cache_size
8
+
9
+ attr_reader :cache_size, :cache_total_hits, :cache_total_queries
10
+
11
+ def fetch(key, language, default = nil, arg = nil) # :nodoc:
12
+
13
+ # use argument as pluralization number, if number
14
+ num = arg.kind_of?(Numeric) ? arg : nil
15
+
16
+ # if there's no translation, use default or original key
17
+ real_default = default || key
18
+
19
+ result = fetch_from_cache(key, language, real_default, num)
20
+
21
+ if num
22
+ return result.sub('%d', num.to_s)
23
+ else
24
+ return arg.nil? ? result : result.sub('%s', arg.to_s)
25
+ end
26
+ end
27
+
28
+ def set(key, language, translations, zero_form = nil) # :nodoc:
29
+ raise ArgumentError, "No language set" if !language
30
+ if translations.kind_of? Array
31
+ translations = [ zero_form ] + translations
32
+ else
33
+ translations = [ zero_form, translations ]
34
+ end
35
+
36
+ idx = 0
37
+ translations.each do |translation|
38
+ set_pluralized(key, language, idx, translation)
39
+ idx += 1
40
+ end
41
+ end
42
+
43
+ def set_pluralized(key, language, idx, translation)
44
+ invalidate_cache(key, language, idx)
45
+ ViewTranslation.transaction do
46
+ old_tr = ViewTranslation.pick(key, language, idx)
47
+ if old_tr
48
+ old_tr.update_attribute(:text, translation)
49
+ else
50
+ ViewTranslation.create!(:tr_key => key,
51
+ :language_id => language.id, :pluralization_index => idx,
52
+ :text => translation)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Returns the number of items in the cache.
58
+ def cache_count
59
+ @cache.size
60
+ end
61
+
62
+ # Resets the cache and its statistics -- for testing.
63
+ def cache_reset
64
+ cache_clear
65
+ @cache_total_hits = 0
66
+ @cache_total_queries = 0
67
+ end
68
+
69
+ private
70
+ def fetch_view_translation(key, language, idx)
71
+ tr = nil
72
+ ViewTranslation.transaction do
73
+ tr = ViewTranslation.pick(key, language, idx)
74
+
75
+ # fill in a nil record for missed translations report
76
+ # do not report missing zero-forms -- they're optional
77
+ if !tr && idx != 0
78
+ tr = ViewTranslation.create!(:tr_key => key,
79
+ :language_id => language.id, :pluralization_index => idx,
80
+ :text => nil)
81
+ end
82
+ end
83
+
84
+ tr ? tr.text : nil
85
+ end
86
+
87
+ def cache_fetch(key, language, idx)
88
+ @cache_total_queries += 1
89
+ cache_key = cache_key(key, language, idx)
90
+ @cache_total_hits += 1 if @cache.has_key?(cache_key)
91
+ @cache[cache_key]
92
+ end
93
+
94
+ def cache_add(key, language, idx, translation)
95
+ cache_clear if @cache_size > max_cache_size * 1024
96
+ size = key.size + (translation.nil? ? 0 : translation.size)
97
+ @cache_size += size
98
+ @cache[cache_key(key, language, idx)] = translation
99
+ end
100
+
101
+ def invalidate_cache(key, language, idx)
102
+ tr = @cache.delete(cache_key(key, language, idx))
103
+ size = key.size + (tr.nil? ? 0 : tr.size)
104
+ @cache_size -= size
105
+ end
106
+
107
+ def cache_key(key, language, idx)
108
+ [ key, language.code, idx ].join(':')
109
+ end
110
+
111
+ def cache_hit_ratio
112
+ @cache_total_hits / @cache_total_queries
113
+ end
114
+
115
+ def cache_clear
116
+ @cache.clear
117
+ @cache_size = 0
118
+ end
119
+
120
+ def initialize
121
+ @cache = {}
122
+ @cache_size = 0
123
+ @cache_total_hits = 0
124
+ @cache_total_queries = 0
125
+
126
+ # default cache size is 8mb
127
+ @max_cache_size = 8192
128
+ end
129
+
130
+ def fetch_from_cache(key, language, real_default, num)
131
+ return real_default if language.nil?
132
+
133
+ zero_form = num == 0
134
+ plural_idx = language.plural_index(num) # language-defined plural form
135
+ zplural_idx = zero_form ? 0 : plural_idx # takes zero-form into account
136
+
137
+ cached = cache_fetch(key, language, zplural_idx)
138
+ if cached
139
+ result = cached
140
+ else
141
+ result = fetch_view_translation(key, language, zplural_idx)
142
+
143
+ # set to plural_form if no zero-form exists
144
+ result ||= fetch_view_translation(key, language, plural_idx) if zero_form
145
+
146
+ cache_add(key, language, zplural_idx, result)
147
+ end
148
+ result ||= real_default
149
+ end
150
+
151
+ end
152
+ end