Mattias Bodlund 1 month ago
parent
commit
2ed1ff41f4
16 changed files with 278 additions and 224 deletions
  1. +2
    -0
      .gitignore
  2. +68
    -68
      Gemfile.lock
  3. +1
    -0
      README DB.txt
  4. +1
    -1
      app/controllers/players_controller.rb
  5. +62
    -26
      app/controllers/stages_controller.rb
  6. +6
    -13
      app/models/concerns/ancestry_with_sorted_url.rb
  7. +6
    -3
      app/models/node.rb
  8. +6
    -2
      app/models/player.rb
  9. +21
    -0
      app/views/stages/bonus.html.erb
  10. +31
    -22
      app/views/stages/result.html.erb
  11. +6
    -3
      config/locales/en.yml
  12. +5
    -2
      config/routes.rb
  13. +1
    -1
      db/migrate/20250527114853_create_players.rb
  14. +2
    -2
      db/schema.rb
  15. +60
    -0
      lib/tasks/nodes.rake
  16. +0
    -81
      lib/tasks/quiz_stats.rake

+ 2
- 0
.gitignore View File

@ -14,8 +14,10 @@
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
/dumps/*
!/log/.keep
!/tmp/.keep
!/dumps/.keep
# Ignore pidfiles, but keep the directory.
/tmp/pids/*


+ 68
- 68
Gemfile.lock View File

@ -3,29 +3,29 @@ GEM
specs:
action_text-trix (2.1.17)
railties
actioncable (8.1.2.1)
actionpack (= 8.1.2.1)
activesupport (= 8.1.2.1)
actioncable (8.1.3)
actionpack (= 8.1.3)
activesupport (= 8.1.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (8.1.2.1)
actionpack (= 8.1.2.1)
activejob (= 8.1.2.1)
activerecord (= 8.1.2.1)
activestorage (= 8.1.2.1)
activesupport (= 8.1.2.1)
actionmailbox (8.1.3)
actionpack (= 8.1.3)
activejob (= 8.1.3)
activerecord (= 8.1.3)
activestorage (= 8.1.3)
activesupport (= 8.1.3)
mail (>= 2.8.0)
actionmailer (8.1.2.1)
actionpack (= 8.1.2.1)
actionview (= 8.1.2.1)
activejob (= 8.1.2.1)
activesupport (= 8.1.2.1)
actionmailer (8.1.3)
actionpack (= 8.1.3)
actionview (= 8.1.3)
activejob (= 8.1.3)
activesupport (= 8.1.3)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (8.1.2.1)
actionview (= 8.1.2.1)
activesupport (= 8.1.2.1)
actionpack (8.1.3)
actionview (= 8.1.3)
activesupport (= 8.1.3)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
@ -33,36 +33,36 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (8.1.2.1)
actiontext (8.1.3)
action_text-trix (~> 2.1.15)
actionpack (= 8.1.2.1)
activerecord (= 8.1.2.1)
activestorage (= 8.1.2.1)
activesupport (= 8.1.2.1)
actionpack (= 8.1.3)
activerecord (= 8.1.3)
activestorage (= 8.1.3)
activesupport (= 8.1.3)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (8.1.2.1)
activesupport (= 8.1.2.1)
actionview (8.1.3)
activesupport (= 8.1.3)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (8.1.2.1)
activesupport (= 8.1.2.1)
activejob (8.1.3)
activesupport (= 8.1.3)
globalid (>= 0.3.6)
activemodel (8.1.2.1)
activesupport (= 8.1.2.1)
activerecord (8.1.2.1)
activemodel (= 8.1.2.1)
activesupport (= 8.1.2.1)
activemodel (8.1.3)
activesupport (= 8.1.3)
activerecord (8.1.3)
activemodel (= 8.1.3)
activesupport (= 8.1.3)
timeout (>= 0.4.0)
activestorage (8.1.2.1)
actionpack (= 8.1.2.1)
activejob (= 8.1.2.1)
activerecord (= 8.1.2.1)
activesupport (= 8.1.2.1)
activestorage (8.1.3)
actionpack (= 8.1.3)
activejob (= 8.1.3)
activerecord (= 8.1.3)
activesupport (= 8.1.3)
marcel (~> 1.0)
activesupport (8.1.2.1)
activesupport (8.1.3)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
@ -247,20 +247,20 @@ GEM
rack (>= 1.3)
rackup (2.3.1)
rack (>= 3)
rails (8.1.2.1)
actioncable (= 8.1.2.1)
actionmailbox (= 8.1.2.1)
actionmailer (= 8.1.2.1)
actionpack (= 8.1.2.1)
actiontext (= 8.1.2.1)
actionview (= 8.1.2.1)
activejob (= 8.1.2.1)
activemodel (= 8.1.2.1)
activerecord (= 8.1.2.1)
activestorage (= 8.1.2.1)
activesupport (= 8.1.2.1)
rails (8.1.3)
actioncable (= 8.1.3)
actionmailbox (= 8.1.3)
actionmailer (= 8.1.3)
actionpack (= 8.1.3)
actiontext (= 8.1.3)
actionview (= 8.1.3)
activejob (= 8.1.3)
activemodel (= 8.1.3)
activerecord (= 8.1.3)
activestorage (= 8.1.3)
activesupport (= 8.1.3)
bundler (>= 1.15.0)
railties (= 8.1.2.1)
railties (= 8.1.3)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
@ -268,9 +268,9 @@ GEM
rails-html-sanitizer (1.7.0)
loofah (~> 2.25)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (8.1.2.1)
actionpack (= 8.1.2.1)
activesupport (= 8.1.2.1)
railties (8.1.3)
actionpack (= 8.1.3)
activesupport (= 8.1.3)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
@ -325,7 +325,7 @@ GEM
ffi (~> 1.12)
logger
securerandom (0.4.1)
sidekiq (8.1.1)
sidekiq (8.1.2)
connection_pool (>= 3.0.0)
json (>= 2.16.0)
logger (>= 1.7.0)
@ -405,17 +405,17 @@ DEPENDENCIES
CHECKSUMS
action_text-trix (2.1.17) sha256=b44691639d77e67169dc054ceacd1edc04d44dc3e4c6a427aa155a2beb4cc951
actioncable (8.1.2.1) sha256=a2f88cecce148b3fcb63d2e517d7694e119830a85baa7d6cf59e5453dcf32e8d
actionmailbox (8.1.2.1) sha256=c2e45c0c1e5687e35e050838c94a8aed0d954c56a32ea411d54cd848c338c54e
actionmailer (8.1.2.1) sha256=d7d62fbc2197f1a7006bb5af4c665edf999adf79ab6c10337c088d27e6622071
actionpack (8.1.2.1) sha256=a6b69cd10ec4c8d978c8eee51206e34152b1c1be017e534236dbc89a3d00ffb8
actiontext (8.1.2.1) sha256=1e503ce600a6ab2e12a46f999959a7d8e2fdaff910ca01dcf3b968934b55d957
actionview (8.1.2.1) sha256=38daa7b87bca427e2967f139e5b7f0d1081271bdafd0e015d8ef97a006f570a6
activejob (8.1.2.1) sha256=c89c311d07fd358b76c581ed8fee87c5b4351fb44994f3389385c014d22182fe
activemodel (8.1.2.1) sha256=8f31a6f9c12fecb8e5a0fce8a8950cfd94f0d75829322935f99e8217a3e5f3c6
activerecord (8.1.2.1) sha256=3f79140318ff6d23376f5d9b1b5b5e2c7d3cc8979dd71367e9a8394378ca630a
activestorage (8.1.2.1) sha256=36794c9b8853ac9276b0386cb1f8973374d8e71e8a9666bb02e70f5b7c9c5391
activesupport (8.1.2.1) sha256=beec20ced12ad569194554399449a6372fdab03061b8f48a9ed6ef9b7dc251b2
actioncable (8.1.3) sha256=e5bc7f75e44e6a22de29c4f43176927c3a9ce4824464b74ed18d8226e75a80f0
actionmailbox (8.1.3) sha256=df7da474eaa0e70df4ed5a6fef66eb3b3b0f2dbf7f14518deee8d77f1b4aae59
actionmailer (8.1.3) sha256=831f724891bb70d0aaa4d76581a6321124b6a752cb655c9346aae5479318448d
actionpack (8.1.3) sha256=af998cae4d47c5d581a2cc363b5c77eb718b7c4b45748d81b1887b25621c29a3
actiontext (8.1.3) sha256=d291019c00e1ea9e6463011fa214f6081a56d7b9a1d224e7d3f6384c1dafc7d2
actionview (8.1.3) sha256=1347c88c7f3edb38100c5ce0e9fb5e62d7755f3edc1b61cce2eb0b2c6ea2fd5d
activejob (8.1.3) sha256=a149b1766aa8204c3c3da7309e4becd40fcd5529c348cffbf6c9b16b565fe8d3
activemodel (8.1.3) sha256=90c05cbe4cef3649b8f79f13016191ea94c4525ce4a5c0fb7ef909c4b91c8219
activerecord (8.1.3) sha256=8003be7b2466ba0a2a670e603eeb0a61dd66058fccecfc49901e775260ac70ab
activestorage (8.1.3) sha256=0564ce9309143951a67615e1bb4e090ee54b8befed417133cae614479b46384d
activesupport (8.1.3) sha256=21a5e0dfbd4c3ddd9e1317ec6a4d782fa226e7867dc70b0743acda81a1dca20e
acts_as_list (1.2.6) sha256=8345380900b7bee620c07ad00991ccee59af3d8c9e8574f426e321da2865fdc8
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
ancestry (5.1.0) sha256=8a073cf6f7e306eeed36af72595abd19602ef4a197bf4beda2f31cf8f55de27b
@ -509,10 +509,10 @@ CHECKSUMS
rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
rails (8.1.2.1) sha256=93ebf1efc792c9bc47e9795259c920312d3920008dad3ae634b7a0457ffe0af8
rails (8.1.3) sha256=6d017ba5348c98fc909753a8169b21d44de14d2a0b92d140d1a966834c3c9cd3
rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89
railties (8.1.2.1) sha256=f4d902869541af4e5b5552d726062fa59ec0fd9078f7ab87720dbd93f22c43ee
railties (8.1.3) sha256=913eb0e0cb520aac687ffd74916bd726d48fa21f47833c6292576ef6a286de22
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
@ -529,7 +529,7 @@ CHECKSUMS
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
sidekiq (8.1.1) sha256=afdf2736883e1e80c1c9b7c47afe0387e1a4997c87170978a45560d35d429151
sidekiq (8.1.2) sha256=4a1266f22bb1dad675d77bf32e8d4c04e990640e3b271650f47ea7299c38fceb
stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73


+ 1
- 0
README DB.txt View File

@ -22,6 +22,7 @@ CREATE EXTENSION hstore;
sudo -u postgres psql -d ikea_foundtation_week_2026
pg_dump -Fc ikea_foundtation_week_2026 > db20140909.dump
pg_dump -U ikea_foundation_2026 -Fc ikea_foundtation_week_2026 > db20140909.dump


+ 1
- 1
app/controllers/players_controller.rb View File

@ -17,7 +17,7 @@ class PlayersController < ApplicationController
def go_again
@player = Player.create(
locale: current_player.locale,
extra_lives_stage: current_player.extra_lives_stage
bonus_points: current_player.bonus_points
)
session[:player_id] = @player.id


+ 62
- 26
app/controllers/stages_controller.rb View File

@ -9,7 +9,7 @@ class StagesController < ApplicationController
before_action :set_outcome, only: [ :reveal, :pick, :result ]
helper_method :stage_index
helper_method :stage_index, :bonus_node
# GET
@ -35,7 +35,7 @@ class StagesController < ApplicationController
# GET Chance or Choice
def reveal
@node = @stage.children.viewable.find_by(template: @outcome)
redirect_to root_path and return if @outcome.blank? or @node.blank?
# redirect_to root_path and return if @outcome.blank? or @node.blank?
render action: @outcome
end
@ -43,18 +43,20 @@ class StagesController < ApplicationController
# POST
def pick
redirect_to root_path and return if @outcome.blank?
# redirect_to root_path and return if @outcome.blank?
@node = @stage.children.viewable.find_by(template: @outcome)
possible_answers = @node.children.viewable
if @outcome == "chance"
case @outcome
when "chance"
result = possible_answers.pluck(:template).sample
elsif @outcome == "choice"
# result = "game_over" if stage_index == 2 # DEV
when "choice"
result = possible_answers.find_by(id: params[:option])&.template
end
current_player.record_pick_result(stage_index, result) unless result.blank?
current_player.record_pick_result(stage_index, result) unless result.blank?
redirect_to action: "result"
end
@ -62,14 +64,19 @@ class StagesController < ApplicationController
# GET
def result
redirect_to root_path and return if @outcome.blank?
# redirect_to root_path and return if @outcome.blank?
@result = current_player.progress[stage_index.to_s]["result"]
@node = @stage.children.viewable.find_by(template: @outcome)&.children&.viewable&.find_by(template: @result)
if current_player.progress[stage_index.to_s].keys.include?("bonus_result")
@result = current_player.progress[stage_index.to_s]&.dig("bonus_result")
@node = @stage.children.viewable.find_by(template: "bonus")&.children&.viewable&.find_by(template: @result)
else
@result = current_player.progress[stage_index.to_s]&.dig("result")
@node = @stage.children.viewable.find_by(template: @outcome)&.children&.viewable&.find_by(template: @result)
current_player.update(is_done: true) if @node.game_over?
current_player.update(is_done: true) if @node&.game_over?
end
redirect_to root_path and return if @node.blank?
# redirect_to root_path if @node.blank?
end
@ -79,33 +86,57 @@ class StagesController < ApplicationController
current_player.update(current_stage: stage_index + 1)
redirect_to stage_path(id: current_player.current_stage)
else
redirect_to root_path
# redirect_to root_path
not_found
end
end
# POST
def bonus
def try_bonus
@result = current_player.progress[stage_index.to_s]["result"]
redirect_to root_path unless @result == "bonus"
# redirect_to root_path and return unless @result == "bonus"
# Set extra lives stage
current_player.update(
extra_lives_stage: ((rand < 0.5) ? [ stage_index - 1, 0 ].max : stage_index),
is_done: true
)
current_player.record_bonus_pick_result(stage_index, nil)
redirect_to action: "bonus"
end
# Reset progress
# current_player.reset_when_game_over
redirect_to action: "game_over"
# GET
def bonus
@result = current_player.progress[stage_index.to_s]["result"]
# redirect_to root_path and return unless @result == "bonus"
@node = bonus_node
end
# POST
def get_extra_life
def pick_bonus
@result = current_player.progress[stage_index.to_s]["result"]
# redirect_to root_path and return unless @result == "bonus"
@node = @stage.children.viewable.find_by(template: "bonus")
possible_answers = @node.children.viewable
result = possible_answers.find_by(id: params[:option])&.template
current_player.record_bonus_pick_result(stage_index, result) unless result.blank?
redirect_to action: "result"
end
# POST
def get_bonus_point
if current_player.progress[stage_index.to_s]&.dig("bonus_result") == "good"
bonus_points_value = stage_index
else
bonus_points_value = [ stage_index - 1, 0 ].max
end
current_player.update(
extra_lives_stage: [ stage_index - 1, 0 ].max,
bonus_points: bonus_points_value,
is_done: true
)
@ -130,7 +161,7 @@ private
return false if stage_index != current_player.current_stage
return true if %w[good best].include?(result)
return true if result == "bad" and current_player.extra_lives_stage >= stage_index
return true if result == "bad" and current_player.bonus_points >= stage_index
false
end
@ -138,7 +169,12 @@ private
def set_stage
@stage = Node.at_depth(1).stage.viewable.ordered[stage_index - 1]
redirect_to root_path and return if @stage.nil?
# redirect_to root_path and return if @stage.nil?
end
def bonus_node
@bonus_node ||= @stage.children&.viewable&.bonus&.first
end


+ 6
- 13
app/models/concerns/ancestry_with_sorted_url.rb View File

@ -3,11 +3,10 @@ module AncestryWithSortedUrl
included do
has_ancestry orphan_strategy: :restrict,
cache_depth: true
cache_depth: true
acts_as_list scope: [:ancestry]
acts_as_list scope: [ :ancestry ]
before_validation :format_slug, :generate_url
@ -15,21 +14,17 @@ module AncestryWithSortedUrl
# validates_uniqueness_of :slug, scope: [:ancestry]
after_commit :update_decendensts_url_if_changed
end
private
def format_slug
I18n.available_locales.each do |l|
l_key = l.to_s.downcase.sub('-', '_')
l_key = l.to_s.downcase.sub("-", "_")
v = self.root? ? l.to_s :
self.send("slug_#{l_key}").blank? ? (self.title(locale: l) || '').parameterize : self.send("slug_#{l_key}").parameterize
self.send("slug_#{l_key}").blank? ? (self.title(locale: l) || "").parameterize : self.send("slug_#{l_key}").parameterize
self.send(:slug=, v, locale: l)
end
@ -39,8 +34,8 @@ private
def generate_url
I18n.available_locales.each do |l|
Mobility.with_locale(l) do
v = File.join(self.ancestors.map { |node| node.slug || '' }, self.slug || '')
v = File.join('', v) #unless I18n.default_locale == l
v = File.join(self.ancestors.map { |node| node.slug || "" }, self.slug || "")
v = File.join("", v) # unless I18n.default_locale == l
self.send(:url=, v, locale: l)
end
end
@ -50,6 +45,4 @@ private
def update_decendensts_url_if_changed
self.reload.descendants.find_each(&:save) if self.previous_changes[:slug].present? or self.previous_changes[:ancestry]
end
end

+ 6
- 3
app/models/node.rb View File

@ -34,7 +34,8 @@ class Node < ApplicationRecord
best: 5,
bad: 6,
good: 7,
game_over: 8
game_over: 8,
score: 9
}
def available_templates
@ -42,14 +43,16 @@ class Node < ApplicationRecord
case depth
when 1
[ :stage, :game_over ]
[ :stage, :game_over, :score ]
when 2
[ :choice, :chance ]
[ :choice, :chance, :bonus ]
when 3
if parent&.chance?
[ :good, :bad, :game_over, :bonus ]
elsif parent&.choice?
[ :best, :good, :bad ]
elsif parent&.bonus?
[ :good, :bad ]
else
[]
end


+ 6
- 2
app/models/player.rb View File

@ -1,6 +1,6 @@
class Player < ApplicationRecord
attribute :current_stage, :integer, default: 1
attribute :extra_lives_stage, :integer, default: 0
attribute :bonus_points, :integer, default: 0
attribute :progress, :json, default: {}
attribute :is_done, :boolean, default: false
@ -37,5 +37,9 @@ class Player < ApplicationRecord
save
end
def record_bonus_pick_result(stage_index, result)
self.progress[stage_index.to_s] ||= {}
self.progress[stage_index.to_s]["bonus_result"] = result
save
end
end

+ 21
- 0
app/views/stages/bonus.html.erb View File

@ -0,0 +1,21 @@
<%-
content_for :title, @node.page_title.blank? ? @node.title : @node.page_title
content_for :meta_description, @node.page_description
content_for :debug, current_player.inspect
%>
<article>
<% @node.attachments.each do |attachment| %>
<%= attachment.body.html_safe %>
<% end %>
<div class="action-container">
<% @node.children.viewable.ordered.each do |answer_option| %>
<%= button_to answer_option.title, url_for(action: 'pick_bonus', option: answer_option.id), method: :post %>
<% end %>
</div>
</article>

+ 31
- 22
app/views/stages/result.html.erb View File

@ -3,38 +3,47 @@
content_for :meta_description, @node.page_description
content_for :debug, current_player.inspect
%>
<article>
<% @node.attachments.each do |attachment| %>
<%= attachment.body.html_safe %>
<% end %>
<%= @node.attachments[(@result == "bad" and current_player.bonus_points >= stage_index) ? 1 : 0]&.body&.html_safe %>
<div class="action-container">
<% case @result %>
<% when "best", "good" %>
<%= button_to t("next_stage"), url_for(action: 'next'), method: :post %>
<% when "bad" %>
<% if current_player.extra_lives_stage >= stage_index %>
<%= button_to t("use_extra_life"), url_for(action: 'next'), method: :post %>
<% else %>
<% if stage_index > 1 %>
<%= button_to t("get_extra_life"), url_for(action: 'get_extra_life'), method: :post %>
<% else %>
<% if @node.parent&.bonus? %>
<%= button_to t("get_bonus_point"), url_for(action: 'get_bonus_point'), method: :post %>
<% else %>
<% case @result %>
<% when "best", "good" %>
<%= button_to t("next_stage"), url_for(action: 'next'), method: :post %>
<% when "bad" %>
<% if current_player.bonus_points >= stage_index %>
<%= button_to t("use_bonus_point"), url_for(action: 'next'), method: :post %>
<% else %>
<% if bonus_node %>
<%= button_to t("bonus"), url_for(action: 'try_bonus'), method: :post %>
<% else %>
<%= button_to t("go_again"), go_again_path(), method: :post %>
<% end %>
<% end %>
<% when "bonus" %>
<%= button_to t("let_my_try"), url_for(action: 'try_bonus'), method: :post %>
<% when "game_over" %>
<% if bonus_node %>
<%= button_to t("bonus"), url_for(action: 'try_bonus'), method: :post %>
<% else %>
<%= button_to t("go_again"), go_again_path(), method: :post %>
<% end %>
<% end %>
<% when "bonus" %>
<%= button_to t("bonus"), url_for(action: 'bonus'), method: :post %>
<% when "game_over" %>
<%= button_to t("go_again"), go_again_path(), method: :post %>
<% end %>
<% end %>
</div>
</article>

+ 6
- 3
config/locales/en.yml View File

@ -4,13 +4,15 @@ en:
client_name: IKEA Foundation
let_me_try: Let me try!
flip_the_card: Flip the card
next_stage: Proceed to the next stage
bonus: Bonus
go_again: Go again
use_extra_life: Use extra life
get_extra_life: Get extra life
use_bonus_point: Use bonus point
get_bonus_point: Get bonus point
let_my_try: Let me try
languages:
en: English
zh: 中文
@ -211,6 +213,7 @@ en:
game_over: Game Over
good: Good
bonus: Bonus
score: Result page
categories:
box: Box


+ 5
- 2
config/routes.rb View File

@ -63,9 +63,12 @@ Rails.application.routes.draw do
get "result", to: "stages#result"
post "next", to: "stages#next"
post "bonus", to: "stages#bonus"
post "get_extra_life", to: "stages#get_extra_life"
post "bonus", to: "stages#try_bonus"
get "bonus", to: "stages#bonus"
post "pick_bonus(/:option)", to: "stages#pick_bonus"
post "get_bonus_point", to: "stages#get_bonus_point"
end
get "game_over", to: "stages#game_over"


+ 1
- 1
db/migrate/20250527114853_create_players.rb View File

@ -3,7 +3,7 @@ class CreatePlayers < ActiveRecord::Migration[8.1]
create_table :players do |t|
t.text :locale
t.integer :current_stage, index: true
t.integer :extra_lives_stage, index: true
t.integer :bonus_points, index: true
t.jsonb :progress, default: {}
t.boolean :is_done, default: false, index: true
t.timestamps


+ 2
- 2
db/schema.rb View File

@ -115,15 +115,15 @@ ActiveRecord::Schema[8.1].define(version: 2025_05_27_114853) do
end
create_table "players", force: :cascade do |t|
t.integer "bonus_points"
t.datetime "created_at", null: false
t.integer "current_stage"
t.integer "extra_lives_stage"
t.boolean "is_done", default: false
t.text "locale"
t.jsonb "progress", default: {}
t.datetime "updated_at", null: false
t.index ["bonus_points"], name: "index_players_on_bonus_points"
t.index ["current_stage"], name: "index_players_on_current_stage"
t.index ["extra_lives_stage"], name: "index_players_on_extra_lives_stage"
t.index ["is_done"], name: "index_players_on_is_done"
end


+ 60
- 0
lib/tasks/nodes.rake View File

@ -0,0 +1,60 @@
namespace :nodes do
desc "Deep copy a node including descendants and attachments. Usage: rake nodes:copy[22]"
task :copy, [ :node_id ] => :environment do |t, args|
node_id = args[:node_id] || 22
begin
source = Node.find(node_id)
puts "Starting deep copy of Node #{source.id}: \"#{source.title}\"..."
# We wrap in a transaction to ensure integrity
new_root = Node.transaction do
copy_node_recursively(source)
end
puts "Successfully created deep copy!"
puts "Source ID: #{source.id}"
puts "New ID: #{new_root.id}"
rescue ActiveRecord::RecordNotFound
puts "Error: Node with ID #{node_id} not found."
rescue => e
puts "Error during copy: #{e.message}"
puts e.backtrace.first(5)
end
end
def copy_node_recursively(source, parent = nil)
# 1. Duplicate the node (this copies attributes including JSONB translations)
new_node = source.dup
# 2. Set parent (Ancestry gem handles the tree logic)
# If no parent provided, we default to the same parent as the source (sibling)
new_node.parent = parent || source.parent
# 3. Modify title of the root copy to distinguish it
if parent.nil?
I18n.available_locales.each do |locale|
title = source.title(locale: locale)
if title.present?
new_node.send(:title=, "#{title} (Copy)", locale: locale)
end
end
end
# 4. Save to get an ID
new_node.save!
# 5. Duplicate Attachments
source.attachments.each do |attachment|
new_attachment = attachment.dup
new_attachment.attachable_for = new_node
new_attachment.save!
end
# 6. Recurse for children
source.children.ordered.each do |child|
copy_node_recursively(child, new_node)
end
new_node
end
end

+ 0
- 81
lib/tasks/quiz_stats.rake View File

@ -1,81 +0,0 @@
# lib/tasks/quiz_stats.rake
namespace :quiz do
desc "Show quiz statistics from Oct 17, 2025"
task stats: :environment do
results = QuizResult.where("created_at >= ?", Date.new(2025, 10, 17))
total = results.count
by_locale = results.group(:locale).count
scores = results.group(:score).count
avg_score = results.average(:score)
# Calculate result categories
questions = Node.at_depth(1).tmpl_article.viewable.ordered.to_a
questions_count = questions.count
result_categories = { "People" => 0, "Planet" => 0, "Balanced" => 0 }
results.each do |qr|
people_score = questions_count - qr.score
score_diff = people_score - qr.score
category = case
when score_diff >= 2
"People"
when score_diff <= -2
"Planet"
else
"Balanced"
end
result_categories[category] += 1
end
puts "Quiz Results from Oct 17, 2025"
puts "=" * 40
puts "Total: #{total}"
puts "\nBy locale:"
by_locale.sort.each do |locale, count|
locale_name = I18n.t(locale, scope: 'languages')
puts " #{locale_name}: #{count}"
end
puts "\nScore distribution:"
scores.sort.each do |score, count|
puts " Score #{score}: #{count}"
end
puts "\nAverage score: #{avg_score&.round(2)}"
puts "\nResult categories:"
result_categories.each do |category, count|
percentage = total > 0 ? (count.to_f / total * 100).round(1) : 0
puts " #{category}: #{count} (#{percentage}%)"
end
# Question by question breakdown
puts "\n\nQuestion breakdown:"
puts "=" * 40
questions.each_with_index do |question, i|
answers = Answer.where(node_id: question.id).where("created_at >= ?", Date.new(2025, 10, 17))
answer_counts = answers.group(:value).count
planet_answer = (i == 0 || i == 3) ? 0 : 1
people_answer = (i == 0 || i == 3) ? 1 : 0
planet_count = answer_counts[planet_answer] || 0
people_count = answer_counts[people_answer] || 0
total_answers = planet_count + people_count
puts "\nQ#{i + 1}: #{question.title}"
if total_answers > 0
planet_pct = (planet_count.to_f / total_answers * 100).round(1)
people_pct = (people_count.to_f / total_answers * 100).round(1)
puts " Planet: #{planet_count} (#{planet_pct}%)"
puts " People: #{people_count} (#{people_pct}%)"
else
puts " No answers"
end
end
end
end

Loading…
Cancel
Save