anima-core 0.2.1 → 1.0.0

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 (280) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +213 -43
  5. data/agents/codebase-analyzer.md +88 -0
  6. data/agents/codebase-pattern-finder.md +83 -0
  7. data/agents/documentation-researcher.md +59 -0
  8. data/agents/thoughts-analyzer.md +102 -0
  9. data/agents/web-search-researcher.md +71 -0
  10. data/anima-core.gemspec +3 -0
  11. data/app/channels/session_channel.rb +195 -45
  12. data/app/decorators/user_message_decorator.rb +16 -5
  13. data/app/jobs/agent_request_job.rb +55 -2
  14. data/app/jobs/analytical_brain_job.rb +33 -0
  15. data/app/jobs/count_event_tokens_job.rb +15 -4
  16. data/app/models/concerns/event/broadcasting.rb +81 -0
  17. data/app/models/event.rb +20 -1
  18. data/app/models/goal.rb +91 -0
  19. data/app/models/session.rb +366 -21
  20. data/config/application.rb +2 -0
  21. data/config/initializers/event_subscribers.rb +0 -1
  22. data/config/routes.rb +0 -6
  23. data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
  24. data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
  25. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  26. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  27. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  28. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  29. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  30. data/db/migrate/20260315140843_create_goals.rb +16 -0
  31. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  32. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  33. data/lib/agent_loop.rb +65 -6
  34. data/lib/agents/definition.rb +116 -0
  35. data/lib/agents/registry.rb +106 -0
  36. data/lib/analytical_brain/runner.rb +276 -0
  37. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  38. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  39. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  40. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  41. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  42. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  43. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  44. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  45. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  46. data/lib/analytical_brain.rb +23 -0
  47. data/lib/anima/cli/mcp/secrets.rb +76 -0
  48. data/lib/anima/cli/mcp.rb +197 -0
  49. data/lib/anima/cli.rb +5 -40
  50. data/lib/anima/installer.rb +168 -0
  51. data/lib/anima/settings.rb +226 -0
  52. data/lib/anima/version.rb +1 -1
  53. data/lib/anima.rb +9 -0
  54. data/lib/credential_store.rb +103 -0
  55. data/lib/environment_probe.rb +232 -0
  56. data/lib/events/subscribers/persister.rb +1 -0
  57. data/lib/events/user_message.rb +17 -0
  58. data/lib/llm/client.rb +29 -10
  59. data/lib/mcp/client_manager.rb +86 -0
  60. data/lib/mcp/config.rb +213 -0
  61. data/lib/mcp/health_check.rb +77 -0
  62. data/lib/mcp/secrets.rb +73 -0
  63. data/lib/mcp/stdio_transport.rb +206 -0
  64. data/lib/providers/anthropic.rb +11 -20
  65. data/lib/shell_session.rb +11 -10
  66. data/lib/skills/definition.rb +97 -0
  67. data/lib/skills/registry.rb +105 -0
  68. data/lib/tools/edit.rb +226 -0
  69. data/lib/tools/mcp_tool.rb +114 -0
  70. data/lib/tools/read.rb +151 -0
  71. data/lib/tools/registry.rb +14 -12
  72. data/lib/tools/request_feature.rb +121 -0
  73. data/lib/tools/return_result.rb +81 -0
  74. data/lib/tools/spawn_specialist.rb +109 -0
  75. data/lib/tools/spawn_subagent.rb +111 -0
  76. data/lib/tools/subagent_prompts.rb +12 -0
  77. data/lib/tools/web_get.rb +8 -9
  78. data/lib/tools/write.rb +86 -0
  79. data/lib/tui/app.rb +985 -26
  80. data/lib/tui/cable_client.rb +69 -31
  81. data/lib/tui/message_store.rb +103 -8
  82. data/lib/tui/screens/chat.rb +293 -45
  83. data/lib/workflows/definition.rb +97 -0
  84. data/lib/workflows/registry.rb +89 -0
  85. data/skills/activerecord/SKILL.md +255 -0
  86. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  87. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  88. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  89. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  90. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  91. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  92. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  93. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  94. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  95. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  96. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  97. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  98. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  99. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  100. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  101. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  102. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  103. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  104. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  105. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  106. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  107. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  108. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  109. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  110. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  111. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  112. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  113. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  114. data/skills/activerecord/references/associations.md +709 -0
  115. data/skills/activerecord/references/basics.md +622 -0
  116. data/skills/activerecord/references/callbacks.md +738 -0
  117. data/skills/activerecord/references/migrations.md +657 -0
  118. data/skills/activerecord/references/querying.md +655 -0
  119. data/skills/activerecord/references/validations.md +596 -0
  120. data/skills/dragonruby/SKILL.md +250 -0
  121. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  122. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  123. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  124. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  125. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  126. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  127. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  128. data/skills/dragonruby/examples/core/labels.rb +22 -0
  129. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  130. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  131. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  132. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  133. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  134. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  135. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  136. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  137. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  138. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  139. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  140. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  141. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  142. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  143. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  144. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  145. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  146. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  147. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  148. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  149. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  150. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  151. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  152. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  153. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  154. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  155. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  156. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  157. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  158. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  159. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  160. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  161. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  162. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  163. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  164. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  165. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  166. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  167. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  168. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  169. data/skills/dragonruby/references/audio.md +396 -0
  170. data/skills/dragonruby/references/core.md +385 -0
  171. data/skills/dragonruby/references/distribution.md +434 -0
  172. data/skills/dragonruby/references/entities.md +516 -0
  173. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  174. data/skills/dragonruby/references/game-logic/state.md +389 -0
  175. data/skills/dragonruby/references/input.md +414 -0
  176. data/skills/dragonruby/references/rendering/animation.md +467 -0
  177. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  178. data/skills/dragonruby/references/scenes.md +443 -0
  179. data/skills/draper-decorators/SKILL.md +344 -0
  180. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  181. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  182. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  183. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  184. data/skills/draper-decorators/references/patterns.md +507 -0
  185. data/skills/draper-decorators/references/testing.md +559 -0
  186. data/skills/gh-issue.md +182 -0
  187. data/skills/mcp-server/SKILL.md +177 -0
  188. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  189. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  190. data/skills/mcp-server/examples/http_client.rb +48 -0
  191. data/skills/mcp-server/examples/http_server.rb +97 -0
  192. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  193. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  194. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  195. data/skills/mcp-server/references/gotchas.md +183 -0
  196. data/skills/mcp-server/references/prompts.md +98 -0
  197. data/skills/mcp-server/references/resources.md +53 -0
  198. data/skills/mcp-server/references/server.md +140 -0
  199. data/skills/mcp-server/references/tools.md +146 -0
  200. data/skills/mcp-server/references/transport.md +104 -0
  201. data/skills/ratatui-ruby/SKILL.md +315 -0
  202. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  203. data/skills/ratatui-ruby/references/events.md +387 -0
  204. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  205. data/skills/ratatui-ruby/references/layout.md +423 -0
  206. data/skills/ratatui-ruby/references/styling.md +268 -0
  207. data/skills/ratatui-ruby/references/testing.md +433 -0
  208. data/skills/ratatui-ruby/references/widgets.md +532 -0
  209. data/skills/rspec/SKILL.md +340 -0
  210. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  211. data/skills/rspec/examples/core/configuration.rb +126 -0
  212. data/skills/rspec/examples/core/hooks.rb +126 -0
  213. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  214. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  215. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  216. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  217. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  218. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  219. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  220. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  221. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  222. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  223. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  224. data/skills/rspec/examples/matchers/change.rb +115 -0
  225. data/skills/rspec/examples/matchers/collections.rb +154 -0
  226. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  227. data/skills/rspec/examples/matchers/composing.rb +155 -0
  228. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  229. data/skills/rspec/examples/matchers/equality.rb +58 -0
  230. data/skills/rspec/examples/matchers/errors.rb +136 -0
  231. data/skills/rspec/examples/matchers/output.rb +103 -0
  232. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  233. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  234. data/skills/rspec/examples/matchers/types.rb +82 -0
  235. data/skills/rspec/examples/matchers/yield.rb +147 -0
  236. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  237. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  238. data/skills/rspec/examples/mocks/constants.rb +177 -0
  239. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  240. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  241. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  242. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  243. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  244. data/skills/rspec/examples/mocks/responses.rb +223 -0
  245. data/skills/rspec/examples/mocks/spies.rb +149 -0
  246. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  247. data/skills/rspec/examples/rails/channels.rb +250 -0
  248. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  249. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  250. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  251. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  252. data/skills/rspec/examples/rails/matchers.rb +374 -0
  253. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  254. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  255. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  256. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  257. data/skills/rspec/examples/rails/transactions.rb +254 -0
  258. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  259. data/skills/rspec/references/core.md +816 -0
  260. data/skills/rspec/references/factory_bot.md +641 -0
  261. data/skills/rspec/references/matchers.md +516 -0
  262. data/skills/rspec/references/mocks.md +381 -0
  263. data/skills/rspec/references/rails.md +528 -0
  264. data/templates/soul.md +40 -0
  265. data/workflows/commit.md +45 -0
  266. data/workflows/create_handoff.md +98 -0
  267. data/workflows/create_note.md +82 -0
  268. data/workflows/create_plan.md +457 -0
  269. data/workflows/decompose_ticket.md +109 -0
  270. data/workflows/feature.md +91 -0
  271. data/workflows/implement_plan.md +87 -0
  272. data/workflows/iterate_plan.md +247 -0
  273. data/workflows/research_codebase.md +210 -0
  274. data/workflows/resume_handoff.md +217 -0
  275. data/workflows/review_pr.md +320 -0
  276. data/workflows/thoughts_init.md +71 -0
  277. data/workflows/validate_plan.md +166 -0
  278. metadata +290 -3
  279. data/app/controllers/api/sessions_controller.rb +0 -25
  280. data/lib/events/subscribers/action_cable_bridge.rb +0 -59
@@ -0,0 +1,507 @@
1
+ # Advanced Draper Patterns
2
+
3
+ ## ApplicationDecorator Base Class
4
+
5
+ Create a base decorator with shared methods:
6
+
7
+ ```ruby
8
+ # app/decorators/application_decorator.rb
9
+ class ApplicationDecorator < Draper::Decorator
10
+ # Common formatting methods
11
+ def formatted_created_at
12
+ h.l(created_at, format: :long)
13
+ end
14
+
15
+ def time_ago
16
+ "#{h.time_ago_in_words(created_at)} ago"
17
+ end
18
+
19
+ def formatted_date(date, format: :long)
20
+ return "N/A" if date.blank?
21
+ h.l(date, format:)
22
+ end
23
+
24
+ def formatted_currency(amount)
25
+ return "$0.00" if amount.blank?
26
+ h.number_to_currency(amount)
27
+ end
28
+
29
+ def truncated_text(text, length: 100)
30
+ h.truncate(text.to_s, length:, separator: ' ')
31
+ end
32
+ end
33
+ ```
34
+
35
+ ## Association Decoration Patterns
36
+
37
+ ### Basic Association
38
+
39
+ ```ruby
40
+ class PostDecorator < ApplicationDecorator
41
+ delegate_all
42
+ decorates_association :author
43
+ decorates_association :comments
44
+ end
45
+
46
+ # In view
47
+ @post.author.full_name # Returns from AuthorDecorator
48
+ @post.comments.each { |c| c.formatted_body } # Each is CommentDecorator
49
+ ```
50
+
51
+ ### With Explicit Decorator
52
+
53
+ ```ruby
54
+ class PostDecorator < ApplicationDecorator
55
+ delegate_all
56
+ decorates_association :author, with: Users::CompactDecorator
57
+ end
58
+ ```
59
+
60
+ ### With Scope
61
+
62
+ ```ruby
63
+ class PostDecorator < ApplicationDecorator
64
+ delegate_all
65
+ decorates_association :comments, scope: :approved
66
+ decorates_association :recent_comments, scope: :recent
67
+ end
68
+ ```
69
+
70
+ ### With Context Propagation
71
+
72
+ ```ruby
73
+ class PostDecorator < ApplicationDecorator
74
+ delegate_all
75
+
76
+ # Static context
77
+ decorates_association :author, context: { display_mode: :compact }
78
+
79
+ # Dynamic context from parent
80
+ decorates_association :comments, context: ->(parent_context) {
81
+ parent_context.merge(post_id: object.id)
82
+ }
83
+ end
84
+ ```
85
+
86
+ ### Delegating Association Methods
87
+
88
+ ```ruby
89
+ class PostDecorator < ApplicationDecorator
90
+ delegate_all
91
+ decorates_association :author
92
+
93
+ delegate :avatar_tag, :profile_link, to: :author, prefix: true
94
+ end
95
+
96
+ # In view
97
+ @post.author_avatar_tag
98
+ @post.author_profile_link
99
+ ```
100
+
101
+ ## Context Patterns
102
+
103
+ ### Controller Context Setup
104
+
105
+ ```ruby
106
+ class ApplicationController < ActionController::Base
107
+ private
108
+
109
+ def decoration_context
110
+ {
111
+ current_user:,
112
+ locale: I18n.locale,
113
+ request_host: request.host
114
+ }
115
+ end
116
+
117
+ helper_method :decorate_with_context
118
+
119
+ def decorate_with_context(object)
120
+ object.decorate(context: decoration_context)
121
+ end
122
+ end
123
+
124
+ class PostsController < ApplicationController
125
+ def show
126
+ @post = decorate_with_context(Post.find(params[:id]))
127
+ end
128
+ end
129
+ ```
130
+
131
+ ### Context-Aware Methods
132
+
133
+ ```ruby
134
+ class ProductDecorator < ApplicationDecorator
135
+ delegate_all
136
+
137
+ def price_display
138
+ if admin_user?
139
+ admin_price_breakdown
140
+ elsif premium_user?
141
+ discounted_price
142
+ else
143
+ standard_price
144
+ end
145
+ end
146
+
147
+ def edit_actions
148
+ return unless can_edit?
149
+ h.content_tag(:div, class: "actions") do
150
+ h.link_to("Edit", h.edit_product_path(object))
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ def admin_user?
157
+ context[:current_user]&.admin?
158
+ end
159
+
160
+ def premium_user?
161
+ context[:current_user]&.premium?
162
+ end
163
+
164
+ def can_edit?
165
+ context[:current_user]&.can?(:edit, object)
166
+ end
167
+ end
168
+ ```
169
+
170
+ ## Collection Decorator Patterns
171
+
172
+ ### Custom Collection Decorator
173
+
174
+ ```ruby
175
+ # app/decorators/paginating_decorator.rb
176
+ class PaginatingDecorator < Draper::CollectionDecorator
177
+ # Kaminari pagination
178
+ delegate :current_page, :total_pages, :limit_value, :entry_name,
179
+ :total_count, :offset_value, :last_page?
180
+
181
+ # Will Paginate
182
+ delegate :total_entries, :per_page, :previous_page, :next_page
183
+ end
184
+
185
+ # In decorator
186
+ class ProductDecorator < ApplicationDecorator
187
+ delegate_all
188
+
189
+ def self.collection_decorator_class
190
+ PaginatingDecorator
191
+ end
192
+ end
193
+
194
+ # Usage
195
+ @products = Product.page(params[:page]).decorate
196
+ @products.current_page # Works!
197
+ ```
198
+
199
+ ### Collection with Summary Methods
200
+
201
+ ```ruby
202
+ class OrdersDecorator < Draper::CollectionDecorator
203
+ delegate :current_page, :total_pages
204
+
205
+ def total_value
206
+ h.number_to_currency(object.sum(&:total))
207
+ end
208
+
209
+ def summary
210
+ "#{object.count} orders totaling #{total_value}"
211
+ end
212
+ end
213
+ ```
214
+
215
+ ## Conditional Rendering Patterns
216
+
217
+ ### State-Based Display
218
+
219
+ ```ruby
220
+ class OrderDecorator < ApplicationDecorator
221
+ delegate_all
222
+
223
+ STATUS_COLORS = {
224
+ pending: "warning",
225
+ processing: "info",
226
+ shipped: "primary",
227
+ delivered: "success",
228
+ cancelled: "danger"
229
+ }.freeze
230
+
231
+ def status_badge
232
+ color = STATUS_COLORS.fetch(status.to_sym, "secondary")
233
+ h.content_tag(:span, status.humanize, class: "badge badge-#{color}")
234
+ end
235
+
236
+ def actions
237
+ case status.to_sym
238
+ when :pending
239
+ pending_actions
240
+ when :processing
241
+ processing_actions
242
+ when :shipped
243
+ shipped_actions
244
+ else
245
+ h.content_tag(:span, "No actions available", class: "text-muted")
246
+ end
247
+ end
248
+
249
+ private
250
+
251
+ def pending_actions
252
+ h.safe_join([
253
+ h.link_to("Process", h.process_order_path(object), method: :patch),
254
+ h.link_to("Cancel", h.cancel_order_path(object), method: :patch)
255
+ ], " | ")
256
+ end
257
+ end
258
+ ```
259
+
260
+ ### Permission-Based Display
261
+
262
+ ```ruby
263
+ class DocumentDecorator < ApplicationDecorator
264
+ delegate_all
265
+
266
+ def download_link
267
+ return access_denied_message unless can_download?
268
+
269
+ h.link_to("Download", h.download_document_path(object), class: "btn btn-primary")
270
+ end
271
+
272
+ def edit_section
273
+ return unless can_edit?
274
+
275
+ h.render("documents/edit_form", document: self)
276
+ end
277
+
278
+ private
279
+
280
+ def can_download?
281
+ return true if public?
282
+ context[:current_user]&.can?(:download, object)
283
+ end
284
+
285
+ def can_edit?
286
+ context[:current_user]&.can?(:edit, object)
287
+ end
288
+
289
+ def access_denied_message
290
+ h.content_tag(:span, "Access denied", class: "text-muted")
291
+ end
292
+ end
293
+ ```
294
+
295
+ ## Decorator Composition
296
+
297
+ ### Multiple Decorators
298
+
299
+ ```ruby
300
+ # Base decorator
301
+ class UserDecorator < ApplicationDecorator
302
+ delegate_all
303
+
304
+ def full_name
305
+ "#{first_name} #{last_name}"
306
+ end
307
+ end
308
+
309
+ # Admin-specific additions
310
+ class Users::AdminDecorator < UserDecorator
311
+ def admin_badge
312
+ return unless admin?
313
+ h.content_tag(:span, "Admin", class: "badge badge-danger")
314
+ end
315
+
316
+ def detailed_info
317
+ h.content_tag(:dl) do
318
+ h.safe_join([
319
+ h.content_tag(:dt, "Email"),
320
+ h.content_tag(:dd, email),
321
+ h.content_tag(:dt, "Role"),
322
+ h.content_tag(:dd, role),
323
+ h.content_tag(:dt, "Last Login"),
324
+ h.content_tag(:dd, formatted_last_login)
325
+ ])
326
+ end
327
+ end
328
+ end
329
+
330
+ # Controller usage
331
+ def index
332
+ @users = User.all.decorate # UserDecorator
333
+ end
334
+
335
+ def admin_index
336
+ @users = Users::AdminDecorator.decorate_collection(User.all)
337
+ end
338
+ ```
339
+
340
+ ### Decorator with Presenters Pattern
341
+
342
+ For complex views requiring multiple models:
343
+
344
+ ```ruby
345
+ # app/presenters/dashboard_presenter.rb
346
+ class DashboardPresenter
347
+ include ActionView::Helpers::TagHelper
348
+ include ActionView::Context
349
+
350
+ attr_reader :user, :orders, :notifications
351
+
352
+ def initialize(user:, orders:, notifications:, view_context:)
353
+ @user = user.decorate
354
+ @orders = orders.decorate
355
+ @notifications = notifications.decorate
356
+ @h = view_context
357
+ end
358
+
359
+ def summary_card
360
+ @h.content_tag(:div, class: "card") do
361
+ @h.safe_join([
362
+ @h.content_tag(:h3, "Welcome, #{user.full_name}"),
363
+ order_summary,
364
+ notification_count
365
+ ])
366
+ end
367
+ end
368
+
369
+ private
370
+
371
+ def order_summary
372
+ @h.content_tag(:p, "#{orders.count} active orders")
373
+ end
374
+
375
+ def notification_count
376
+ @h.content_tag(:p, "#{notifications.unread.count} unread notifications")
377
+ end
378
+ end
379
+ ```
380
+
381
+ ## Draper with Turbo/Hotwire
382
+
383
+ ### Broadcast Compatibility
384
+
385
+ Draper automatically decorates objects in Turbo broadcasts:
386
+
387
+ ```ruby
388
+ class Comment < ApplicationRecord
389
+ after_create_commit -> {
390
+ broadcast_prepend_to post, :comments
391
+ # Object is auto-decorated with CommentDecorator
392
+ }
393
+ end
394
+ ```
395
+
396
+ ### Stimulus Integration
397
+
398
+ ```ruby
399
+ class FormDecorator < ApplicationDecorator
400
+ delegate_all
401
+
402
+ def input_wrapper(field, &block)
403
+ h.content_tag(:div,
404
+ class: "form-group",
405
+ data: {
406
+ controller: "form-field",
407
+ form_field_field_value: field
408
+ },
409
+ &block
410
+ )
411
+ end
412
+ end
413
+ ```
414
+
415
+ ## Caching Decorated Output
416
+
417
+ ```ruby
418
+ class ProductDecorator < ApplicationDecorator
419
+ delegate_all
420
+
421
+ def cached_price_display
422
+ h.cache(["product_price", object, context[:locale]]) do
423
+ complex_price_calculation
424
+ end
425
+ end
426
+
427
+ private
428
+
429
+ def complex_price_calculation
430
+ # Expensive formatting logic
431
+ h.content_tag(:div, class: "price-display") do
432
+ # ...
433
+ end
434
+ end
435
+ end
436
+ ```
437
+
438
+ ## Internationalization
439
+
440
+ ```ruby
441
+ class OrderDecorator < ApplicationDecorator
442
+ delegate_all
443
+
444
+ def status_text
445
+ I18n.t("orders.status.#{status}", default: status.humanize)
446
+ end
447
+
448
+ def formatted_total
449
+ h.number_to_currency(total, locale: context[:locale] || I18n.locale)
450
+ end
451
+
452
+ def delivery_date_text
453
+ return I18n.t("orders.no_delivery_date") if delivery_date.blank?
454
+
455
+ I18n.l(delivery_date, format: :long)
456
+ end
457
+ end
458
+ ```
459
+
460
+ ## Serialization
461
+
462
+ ### JSON Serialization
463
+
464
+ ```ruby
465
+ class ProductDecorator < ApplicationDecorator
466
+ delegate_all
467
+
468
+ # Override serializable_hash to include decorator methods
469
+ def serializable_hash(options = nil)
470
+ super(options).merge(
471
+ formatted_price: formatted_price,
472
+ display_name: display_name
473
+ )
474
+ end
475
+ end
476
+
477
+ # Usage
478
+ @product.decorate.to_json
479
+ # => {"id":1,"name":"Widget","formatted_price":"$10.00","display_name":"Widget (SKU-123)"}
480
+ ```
481
+
482
+ ### API Response Formatting
483
+
484
+ ```ruby
485
+ class Api::ProductDecorator < ApplicationDecorator
486
+ delegate_all
487
+
488
+ def as_json(options = {})
489
+ {
490
+ id:,
491
+ name:,
492
+ price: formatted_price,
493
+ availability: availability_status,
494
+ links: {
495
+ self: h.api_product_url(object),
496
+ category: h.api_category_url(category)
497
+ }
498
+ }
499
+ end
500
+
501
+ private
502
+
503
+ def availability_status
504
+ in_stock? ? "available" : "out_of_stock"
505
+ end
506
+ end
507
+ ```