write MVC-oriented Ruby code by putting models and controllers in the expected locations. Most will add utility modules with class-methods in lib/, but that’s it. It takes 2-3 years before developers realize: “Rails is just Ruby. I can create simple objects and compose them in ways that Rails does not explicitly endorse! Mike Perham (the author and maintainer of Sidekiq) https://www.mikeperham.com/2012/05/05/five-common-rails-mistakes/
for complicated issues, that caused by previous “simple solution” • thus each iteration is making entire system even more complex • but they call it a “sharp knife” and it’s all your fault to end up without 2 fingers!
Indirect business logic flow (I know you write BL in Model :) ) Rails Way solution - SUPPRESS Notification.suppress do Relationship.create(follower: self, followed: user_to_follow) end https://medium.com/spritle-software/rails-5-activerecord-suppress-a-step-too-far-d7ec2e4ed027
Don’t get me wrong: • I like how it looks and how easy it is to write • What I don’t like ◦ Hidden internal complexity ◦ Shared state (timezone from current thread, internal call to Time.now) ◦ Architectural influence ◦ The need to “fix” this behaviour (via TimeCop gem)
with Rails Way - blame it • Unit tests are not about testing, but about code architecture • Unit test is the best independent consumer of your code • If you can’t unit test your code - smth is wrong in your code, but not with Unit tests idea • Capybara test are good, but slow and require refactoring as soon as your HTML/CSS changes http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html
“Untitled Song” property :written_at, type: Types::Form::DateTime property :country, writeable: false property :song_uuid, virtual: true validates :written_at, presence: true def title=(value) super sanitize(value) # value is raw form input. end end
property :id, on: :album property :title, on: :album property :songs, on: :cd property :cd_id, on: :cd, from: :id validates :title, presence: true end form = AlbumForm.new(album: album, cd: CD.find(1))
contract do # All that Reform stuff goes here with properties and validates end step Policy::Guard( :authorize! ) step Model( BlogPost, :new ) step Contract::Build() step Contract::Validate() step Contract::Persist() step :notify! def authorize!(options, current_user:, **) current_user.signed_in? end def notify!(options, current_user:, model:, **) BlogPost::Notification.(current_user, model) end end
step :how_are_you? success :enjoy_your_day! failure :tell_joke! def hello_world!(options, *) end def how_are_you?(options, params:, **) params[:happy] == "yes" end def enjoy_your_day!(options, *) end def tell_joke!(options, *) end end end
:send_instructions puts "PIPETREE:" puts self["pipetree"].inspect [ >operation.new >contract.build >contract.default.validate >find_user >send_instructions >contract.build >contract.default.validate >find_user >send_instructions ] Change Operation class and Class would be reloaded -> steps called one more time
trailblazer_loader gem • It has specific order • Models are loaded before Operations • If you need specific Operations loading order explicit require_dependency • It is needed if one Operation inherit from another one require_dependency("#{File.dirname(__FILE__)}/sign_up_form.rb")
• Model contains only Queries, Scopes and Relations. • No callbacks in Models! • Controller actions have only Operation calls and result render • No business logic in Controller, any data manipulation • No Strong Params used in Controller • All validation in only inside Operations • View helpers not used • All view logic placed in Cells • Shared view logic in Decorators • Mostly Cell and Operation builders used instead of if/then/else in Controller or view