weeler 1.4.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +13 -3
  3. data/CHANGELOG.md +24 -0
  4. data/Rakefile +4 -0
  5. data/app/controllers/weeler/translations_controller.rb +13 -7
  6. data/app/views/weeler/translations/_translation.html.haml +2 -2
  7. data/app/views/weeler/translations/edit.html.haml +3 -3
  8. data/app/views/weeler/translations/new.html.haml +2 -2
  9. data/app/views/weeler/translations/usage_stats.html.haml +10 -4
  10. data/lib/generators/weeler/templates/migrations/create_weeler_locks.rb +14 -0
  11. data/lib/generators/weeler/templates/migrations/create_weeler_translation_stats.rb +11 -0
  12. data/lib/i18n/backend/weeler.rb +15 -6
  13. data/lib/i18n/backend/weeler/lock.rb +78 -0
  14. data/lib/i18n/backend/weeler/translation_stat.rb +25 -0
  15. data/lib/i18n/backend/weeler/usage_logger.rb +17 -1
  16. data/lib/weeler/action_controller/acts/restful.rb +1 -1
  17. data/lib/weeler/engine.rb +1 -1
  18. data/lib/weeler/version.rb +2 -2
  19. data/spec/.gitignore +21 -0
  20. data/spec/dummy/config/database.yml +2 -24
  21. data/spec/dummy/db/migrate/20160330161101_create_weeler_locks.rb +14 -0
  22. data/spec/dummy/db/migrate/20160330192005_create_weeler_translation_stats.rb +11 -0
  23. data/spec/dummy/db/schema.rb +31 -11
  24. data/spec/dummy/log/development.log +780 -0
  25. data/spec/spec_helper.rb +1 -1
  26. data/spec/weeler/action_controller/acts/restful_spec.rb +1 -1
  27. data/spec/weeler/i18n/backend/weeler/lock_spec.rb +89 -0
  28. data/spec/weeler/i18n/backend/weeler/usage_logger_spec.rb +34 -0
  29. data/spec/weeler/i18n/backend/weeler_spec.rb +22 -0
  30. data/weeler.gemspec +2 -2
  31. metadata +18 -8
  32. data/spec/dummy/db/development.sqlite3 +0 -0
  33. data/spec/dummy/db/test.sqlite3 +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 04354c614713ac3f01bbd9ef74db3d3adfbf6e5e
4
- data.tar.gz: b7cb66aa043d0f4474d030fc8a3098c2f110eeb4
3
+ metadata.gz: 21fd389096c8af80e13c46bf777233d15188fa71
4
+ data.tar.gz: 783c33428045e4d05b7e76fc10a991cafc3f9b2e
5
5
  SHA512:
6
- metadata.gz: 3995b5e005fae4e1d03fa0bcbee009fd7cfa0ace18d0be818250c690b6afec317ed9ee0b31f213a19c112308463aa039e1be74ea53dd420162833e6ff7e7cc8f
7
- data.tar.gz: aca2e0510b49b00a85d99a8329eec36874b10105ead3cd20b13da590a68f38b0c43056ea6424265a3033c85c575b731b944044a02917e2124380416a02f31c84
6
+ metadata.gz: 39c2ceb91177c876eadeb18b9e03b86fc8367c33a48cc81f4b2e627741290816c5d8ecd8c20188c87e1f776e3e6df7f1f16e19f154007464e4200d9b25123ee1
7
+ data.tar.gz: e9602c77a0ea9738ae4eb0c8dd91c98bdfb228a594e9ead83d41b0bcbf4bb0a799f19fabeb8c2c479c0e07b0be4b148b0da8b19388d4e8bd47edfb8954f187e4
@@ -1,4 +1,11 @@
1
1
  language: ruby
2
+ before_install:
3
+ - rvm get head
4
+ services:
5
+ - postgresql
6
+ sudo: false
7
+ before_script:
8
+ - psql -c 'create database travis_ci_test;' -U postgres
2
9
  rvm:
3
10
  - "2.0.0"
4
11
  - "2.1.0"
@@ -11,7 +18,10 @@ rvm:
11
18
  - "2.2.0"
12
19
  - "2.2.1"
13
20
  - "2.2.2"
14
- - "jruby"
15
- - "rbx-2.4.1"
21
+ - "2.2.3"
22
+ - "2.2.4"
23
+ - "2.3.0"
16
24
  # uncomment this line if your project needs to run something other than `rake`:
17
- script: bundle exec rspec spec
25
+ script:
26
+ - RAILS_ENV=test bundle exec rake db:migrate --trace
27
+ - bundle exec rspec spec
@@ -1,3 +1,27 @@
1
+ ## 1.5.1
2
+
3
+ * Fixed version number
4
+
5
+ ### Contributors
6
+
7
+ * Artis Raugulis
8
+
9
+ ## 1.4.0
10
+
11
+ * Added translation usage stats
12
+ * Fixed a transliteration bug
13
+
14
+ ### Contributors
15
+
16
+ * Artis Raugulis
17
+
18
+ ## 1.5.0
19
+
20
+ * Added Lock
21
+ * Added translation usage stats export
22
+ * Dropped jruby support
23
+ * Dropped rbx suport
24
+
1
25
  ## 1.4.0
2
26
 
3
27
  * Added translation usage stats
data/Rakefile CHANGED
@@ -1 +1,5 @@
1
+ Bundler::GemHelper.install_tasks
2
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
3
+ load 'rails/tasks/engine.rake'
4
+
1
5
  require "bundler/gem_tasks"
@@ -20,7 +20,7 @@ module Weeler
20
20
 
21
21
  if @translation.save
22
22
  Settings.i18n_updated_at = Time.now
23
- redirect_to edit_weeler_translation_path(@translation), flash: {success: "Translation saved."}
23
+ redirect_to ({ action: :edit, id: @translation }), flash: { success: "Translation saved." }
24
24
  else
25
25
  flash.now[:error] = "Errors in saving."
26
26
  render :edit
@@ -33,7 +33,7 @@ module Weeler
33
33
  if @translation.update_attributes(translation_params)
34
34
  Settings.i18n_updated_at = Time.now
35
35
 
36
- redirect_to edit_weeler_translation_path(@translation), flash: {success: "Translation updated."}
36
+ redirect_to ({ action: :edit, id: @translation }), flash: { success: "Translation updated." }
37
37
  else
38
38
  flash.now[:error] = "Errors in updating."
39
39
  render :edit
@@ -46,13 +46,19 @@ module Weeler
46
46
 
47
47
  Settings.i18n_updated_at = Time.now
48
48
 
49
- redirect_to weeler_translations_path, flash: {success: "Translation succesfully removed."}
49
+ redirect_to ({ action: :index }), flash: {success: "Translation succesfully removed."}
50
50
  end
51
51
 
52
52
  def usage_stats
53
53
  @used_keys = []
54
- Weeler.i18n_cache.instance_variable_get(:@data).keys.each do |key|
55
- @used_keys << [key, Weeler.i18n_cache.read(key)] if key.start_with?('usage_stats')
54
+ if Settings.log_key_usage == 'dump'
55
+ I18n::Backend::Weeler::TranslationStat.all.each do |translation_stat|
56
+ @used_keys << [translation_stat.key, translation_stat.usage_count]
57
+ end
58
+ else
59
+ Weeler.i18n_cache.instance_variable_get(:@data).keys.each do |key|
60
+ @used_keys << [key, Weeler.i18n_cache.read(key)] if key.start_with?('usage_stats')
61
+ end
56
62
  end
57
63
  end
58
64
 
@@ -72,9 +78,9 @@ module Weeler
72
78
 
73
79
  Settings.i18n_updated_at = Time.now
74
80
 
75
- redirect_to weeler_translations_path, flash: {success: "Translations succesfully imported."}
81
+ redirect_to ({ action: :index }), flash: {success: "Translations succesfully imported."}
76
82
  else
77
- redirect_to weeler_translations_path, flash: {success: "No file choosen"}
83
+ redirect_to ({ action: :index }), flash: {success: "No file choosen"}
78
84
  end
79
85
  end
80
86
 
@@ -8,5 +8,5 @@
8
8
  %td= translation.interpolations
9
9
  %td.text-center= translation.created_at.strftime("%d/%m/%Y")
10
10
  %td
11
- = link_to 'Edit', edit_weeler_translation_path(translation.id), class: 'btn btn-default'
12
- = link_to 'Remove', weeler_translation_path(translation), data: { confirm: 'Are you sure you want to remove?' }, method: :delete, class: 'btn btn-danger'
11
+ = link_to 'Edit', { action: :edit, id: translation.id }, class: 'btn btn-default'
12
+ = link_to 'Remove', { action: :destroy, id: translation }, data: { confirm: 'Are you sure you want to remove?' }, method: :delete, class: 'btn btn-danger'
@@ -6,9 +6,9 @@
6
6
  .edit_user.container
7
7
  .row
8
8
  .col-lg-12.col-md-12
9
- = form_for @translation, {url: weeler_translation_path(@translation), html: {class: "form-horizontal", role: "form"}} do |f|
9
+ = form_for @translation, {url: { action: :update, id: @translation }, html: {class: "form-horizontal", role: "form"}} do |f|
10
10
  = render partial: "form", locals: {translation: @translation, f: f}
11
11
  %p.text-center
12
12
  %button.btn.btn-primary{type: "submit"} Save
13
- %a.btn.btn-link{href: weeler_translations_path} Cancel
14
- = link_to 'Remove', weeler_translation_path(@translation), :confirm => "Are you sure you want remove?", :method => :delete, :class => "btn btn-danger"
13
+ = link_to 'Cancel', { action: :index }, class: 'btn btn-link'
14
+ = link_to 'Remove', { action: :destroy, id: @translation}, :confirm => "Are you sure you want remove?", :method => :delete, :class => "btn btn-danger"
@@ -6,8 +6,8 @@
6
6
  .edit_user.container
7
7
  .row
8
8
  .col-lg-12.col-md-12
9
- = form_for @translation, {url: weeler_translations_path, html: {class: "form-horizontal", role: "form"}} do |f|
9
+ = form_for @translation, {url: { action: :create }, html: {class: "form-horizontal", role: "form"}} do |f|
10
10
  = render partial: "form", locals: {translation: @translation, f: f}
11
11
  %p.text-center
12
12
  %button.btn.btn-primary{type: "submit"} Save
13
- %a.btn.btn-link{href: weeler_translations_path} Cancel
13
+ = link_to 'Cancel', { action: :index }, class: 'btn btn-link'
@@ -22,7 +22,13 @@
22
22
  %th text
23
23
  %tbody
24
24
  - @used_keys.each do |key, usage|
25
- %tr
26
- %td= usage
27
- %td= key.to_s.gsub('usage_stats/en/', '')
28
- %td= t(key.to_s.gsub('usage_stats/en/', ''))
25
+ - begin
26
+ %tr
27
+ %td= usage
28
+ %td= key.to_s.gsub('usage_stats/en/', '')
29
+ %td= CGI::unescapeHTML(CGI::escapeHTML(t(key.to_s.gsub('usage_stats/en/', ''))))
30
+ - rescue => e
31
+ %tr
32
+ %td= "err #{usage}"
33
+ %td= key
34
+ %td= e.message
@@ -0,0 +1,14 @@
1
+ class CreateWeelerLocks < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :weeler_locks do |t|
4
+ t.string :name, :limit => 40
5
+ t.timestamps
6
+ end
7
+ add_index :weeler_locks, :name, :unique => true
8
+ end
9
+
10
+ def self.down
11
+ remove_index :weeler_locks, :column => :name
12
+ drop_table :weeler_locks
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ class CreateWeelerTranslationStats < ActiveRecord::Migration
2
+ def change
3
+ create_table :weeler_translation_stats do |t|
4
+ t.string :key
5
+ t.integer :usage_count, default: 0
6
+
7
+ t.timestamps
8
+ end
9
+ add_index :weeler_translation_stats, :key
10
+ end
11
+ end
@@ -5,6 +5,8 @@ require 'i18n/backend/weeler/html_checker'
5
5
  require 'i18n/backend/weeler/exporter'
6
6
  require 'i18n/backend/weeler/importer'
7
7
  require 'i18n/backend/weeler/usage_logger'
8
+ require 'i18n/backend/weeler/translation_stat'
9
+ require 'i18n/backend/weeler/lock'
8
10
 
9
11
  module I18n
10
12
  module Backend
@@ -24,12 +26,14 @@ module I18n
24
26
 
25
27
  PLURAL_KEYS = ["zero", "one", "other"]
26
28
 
27
- autoload :HtmlChecker, 'i18n/backend/weeler/html_checker'
28
- autoload :Translation, 'i18n/backend/weeler/dedupe'
29
- autoload :Translation, 'i18n/backend/weeler/translation'
30
- autoload :Exporter, 'i18n/backend/weeler/exporter'
31
- autoload :Importer, 'i18n/backend/weeler/importer'
32
- autoload :UsageLogger, 'i18n/backend/weeler/usage_logger'
29
+ autoload :HtmlChecker, 'i18n/backend/weeler/html_checker'
30
+ autoload :Translation, 'i18n/backend/weeler/dedupe'
31
+ autoload :Translation, 'i18n/backend/weeler/translation'
32
+ autoload :Exporter, 'i18n/backend/weeler/exporter'
33
+ autoload :Importer, 'i18n/backend/weeler/importer'
34
+ autoload :UsageLogger, 'i18n/backend/weeler/usage_logger'
35
+ autoload :TranslationStat, 'i18n/backend/weeler/translation_stat'
36
+ autoload :Lock, 'i18n/backend/weeler/lock'
33
37
 
34
38
  module Implementation
35
39
  include Base, Flatten
@@ -72,9 +76,14 @@ module I18n
72
76
 
73
77
  # log locale/key usage for statistics
74
78
  if Settings.log_key_usage == 'true'
79
+ i18n_cache.delete([:dump_usage_stats, Process.pid])
75
80
  log_key_usage(locale, key)
76
81
  end
77
82
 
83
+ if Settings.log_key_usage == 'dump'
84
+ dump_key_usage
85
+ end
86
+
78
87
  return nil if i18n_cache.read([:missing, [locale, key]])
79
88
 
80
89
  keys = expand_keys key
@@ -0,0 +1,78 @@
1
+ require 'active_record'
2
+
3
+ module I18n
4
+ module Backend
5
+ # Weeler model used to store locks
6
+ #
7
+ # This model expects a table like the following to be already set up in
8
+ # your the database:
9
+ #
10
+ # def self.up
11
+ # create_table :weeler_locks do |t|
12
+ # t.string :name, :limit => 40
13
+ # t.timestamps
14
+ # end
15
+ # add_index :weeler_locks, :name, :unique => true
16
+ # end
17
+ #
18
+ # def self.down
19
+ # remove_index :weeler_locks, :column => :name
20
+ # drop_table :weeler_locks
21
+ # end
22
+ #
23
+ class Weeler
24
+ class Lock < ActiveRecord::Base
25
+
26
+ self.table_name = 'weeler_locks'
27
+
28
+ def self.acquire(name)
29
+ already_acquired = definitely_acquired?(name)
30
+
31
+ if already_acquired
32
+ yield
33
+ else
34
+ begin
35
+ create(:name => name) unless find_by(name: name)
36
+ rescue ActiveRecord::StatementInvalid
37
+ # concurrent create is okay
38
+ end
39
+
40
+ begin
41
+ result = nil
42
+ transaction do
43
+ self.lock(true).find_by(name: name) # this is the call that will block
44
+ acquired_lock(name)
45
+ result = yield
46
+ end
47
+
48
+ result
49
+ ensure
50
+ maybe_released_lock(name)
51
+ end
52
+ end
53
+ end
54
+
55
+ # if true, the lock is acquired
56
+ # if false, the lock might still be acquired, because we were in another db transaction
57
+ def self.definitely_acquired?(name)
58
+ !!Thread.current[:definitely_acquired_locks] and Thread.current[:definitely_acquired_locks].has_key?(name)
59
+ end
60
+
61
+ def self.acquired_lock(name)
62
+ logger.debug("Acquired lock '#{name}'")
63
+ Thread.current[:definitely_acquired_locks] ||= {}
64
+ Thread.current[:definitely_acquired_locks][name] = true
65
+ end
66
+
67
+ def self.maybe_released_lock(name)
68
+ logger.debug("Released lock '#{name}' (if we are not in a bigger transaction)")
69
+ Thread.current[:definitely_acquired_locks] ||= {}
70
+ Thread.current[:definitely_acquired_locks].delete(name)
71
+ end
72
+
73
+ private_class_method :acquired_lock, :maybe_released_lock
74
+
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,25 @@
1
+ require 'active_record'
2
+
3
+ module I18n
4
+ module Backend
5
+ # Weeler model used to store translation key usage statistics
6
+ #
7
+ # This model expects a table like the following to be already set up in
8
+ # your the database:
9
+ #
10
+ # create_table :weeler_translation_stats do |t|
11
+ # t.string :key
12
+ # t.integer :usage_count, default: 0
13
+ # end
14
+ #
15
+ class Weeler
16
+ class TranslationStat < ::ActiveRecord::Base
17
+
18
+ self.table_name = 'weeler_translation_stats'
19
+
20
+ validates :key, uniqueness: true
21
+ validates :key, :usage_count, presence: true
22
+ end
23
+ end
24
+ end
25
+ end
@@ -11,7 +11,23 @@ module I18n
11
11
  value = existing.present? ? (existing.to_i + 1) : 1
12
12
  i18n_cache.write [:usage_stats, [locale, key]], value
13
13
  end
14
-
14
+
15
+ def dump_key_usage
16
+ already_dumped = i18n_cache.read([:dump_usage_stats, Process.pid]).present?
17
+ unless already_dumped
18
+ Weeler::Lock.acquire('usage_stats_dump') do
19
+ i18n_cache.instance_variable_get(:@data).keys.each do |key|
20
+ if key.start_with?('usage_stats')
21
+ translation_stat = TranslationStat.find_or_create_by(key: key.gsub('usage_stats/en/', ''))
22
+ translation_stat.usage_count += i18n_cache.read(key).to_i
23
+ translation_stat.save!
24
+ end
25
+ end
26
+ i18n_cache.write [:dump_usage_stats, Process.pid], Process.pid
27
+ end
28
+ end
29
+ end
30
+
15
31
  Weeler.send(:include, UsageLogger)
16
32
  end
17
33
  end
@@ -118,7 +118,7 @@ module Weeler
118
118
  permited_params.call(params)
119
119
  elsif permited_params.blank?
120
120
  warning_suggestion = params[parameterized_name.to_sym].is_a?(Hash) ? params[parameterized_name.to_sym].keys.map{ |k| k.to_sym } : "permit_params:"
121
- warn "[UNPERMITED PARAMS] To permiting #{params[parameterized_name.to_sym].inspect} params, add 'permit_params: #{warning_suggestion}' option to 'acts_as_restful'"
121
+ warn "[UNPERMITED PARAMS] To permit #{params[parameterized_name.to_sym].inspect} params, add 'permit_params: #{warning_suggestion}' option to 'acts_as_restful'"
122
122
  else
123
123
  params.require(parameterized_name.to_sym).permit(permited_params)
124
124
  end
@@ -10,7 +10,7 @@ module Weeler
10
10
  config.weeler = Weeler
11
11
 
12
12
  config.i18n.available_locales = [:en] unless config.i18n.available_locales.present?
13
-
13
+ config.active_record.raise_in_transactional_callbacks = true
14
14
  config.assets.precompile += ["weeler/init.js", "weeler/init.css"]
15
15
 
16
16
  # Load extend Rails classes
@@ -1,8 +1,8 @@
1
1
  module Weeler
2
2
  module VERSION
3
3
  MAJOR = 1
4
- MINOR = 4
5
- TINY = 0
4
+ MINOR = 5
5
+ TINY = 1
6
6
  PRE = nil
7
7
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
8
8
  end
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .coveralls.yml
19
+ .DS_Store
20
+ spec/dummy/log/test.log
21
+ rspec.html
@@ -1,25 +1,3 @@
1
- # SQLite version 3.x
2
- # gem install sqlite3
3
- #
4
- # Ensure the SQLite 3 gem is defined in your Gemfile
5
- # gem 'sqlite3'
6
- development:
7
- adapter: sqlite3
8
- database: db/development.sqlite3
9
- pool: 5
10
- timeout: 5000
11
-
12
- # Warning: The database defined as "test" will be erased and
13
- # re-generated from your development database when you run "rake".
14
- # Do not set this db to the same as development or production.
15
1
  test:
16
- adapter: sqlite3
17
- database: db/test.sqlite3
18
- pool: 5
19
- timeout: 5000
20
-
21
- production:
22
- adapter: sqlite3
23
- database: db/production.sqlite3
24
- pool: 5
25
- timeout: 5000
2
+ adapter: postgresql
3
+ database: travis_ci_test