Coder Social home page Coder Social logo

granite's Introduction

Granite

Granite is an alternative Rails application architecture framework.

Build Status

Installation

Add this line to your application's Gemfile:

gem 'granite'

And then execute:

$ bundle

Or install it yourself as:

$ gem install granite

Usage

Please see our official documentation or check the granite application example.

Versioning

We use semantic versioning for our releases.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/toptal/granite.

This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

Running specs

To run specs you can run

bin/setup
docker-compose up
rspec

Using Granite's Rubocop config

Add this to your Rubocop config file:

require:
  - rubocop-granite

This will add config for Lint/UselessAccessModifier to treat projector as separate context. It is equivalent to:

Lint/UselessAccessModifier:
  ContextCreatingMethods:
    - projector

License

Granite is released under the MIT License.

granite's People

Contributors

alecksjohannes avatar almaron avatar andriusch avatar barthez avatar catks avatar dependabot-preview[bot] avatar dependabot[bot] avatar detomastah avatar dziulius avatar ebeagusamuel avatar gusaiani avatar id-ilych avatar jaimerson avatar jarosluv avatar javier-delgado avatar jodosha avatar jonatas avatar juike avatar lstrzebinczyk avatar mbie avatar mdkalish avatar mpapis avatar ojab avatar pirj avatar pluff avatar ronin avatar serbantarmure avatar sl4vr avatar svetoptal avatar zinovyev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

granite's Issues

Support using granite in engines

Steps to reproduce

  • Generate mountable, isolated engine
  • add granite action in engines/engine_name/config/routes.rb:
EngineName::Engine.routes.draw do
  resources :somethings, only: [] do
    member do
      granite 'engine_name/somethings/action_name#modal'
    end
  end
end

  • get an exception when linking
Seems like EngineName::Somethings::ActionName::ModalProjector was not mounted. Do you have engine_name/somethings/action_name#modal declared in routes?
granite (0.9.2) lib/granite/projector/helpers.rb:55:in `fetch_corresponding_route'
granite (0.9.2) lib/granite/projector/helpers.rb:43:in `corresponding_route'
granite (0.9.2) lib/granite/projector/helpers.rb:37:in `required_params'
granite (0.9.2) lib/granite/projector/helpers.rb:22:in `action_url'
granite (0.9.2) lib/granite/projector/helpers.rb:31:in `action_path'
granite (0.9.2) lib/granite/projector/controller_actions.rb:30:in `confirm_path'

The answer is: yes, I have it declared but the engine is isolated and the granite cache does not seem to handle it:

      def fetch_corresponding_route
        Rails.application.routes.routes.granite_cache[*route_id] || fail(ActionNotMountedError, self)
      end

Resources

Represents doesn't work with alias_attribute

create_table "users", force: :cascade do |t|
  t.integer :sign_in_count
end

class User < ActiveRecord::Base
  alias_attribute :sign_ins, :sign_in_count
end

class DummyAction
  subject :user
  represents :sign_ins, of: :user
end

Since we use represents on aliased attribute, it doesn't correctly determine type and doesn't typecast the attribute correctly:

action = DummyAction.new(user, sign_ins: '13')
action.sign_ins #=> "13"

Projector with `Granite.view_context` is always `nil`

I'm trying to create the first projector for the example_granite_application and it fails when I try to inject the current_user as the performer.

In a simple version, I'm creating an InlineProjector and trying to use it:

class InlineProjector < Granite::Projector

  post :perform, as: '' do
    if action.perform!
      redirect_to projector.success_redirect, notice: t('.notice')
    else
      messages = projector.action.errors.full_messages.to_sentence
      redirect_to projector.failure_redirect, alert:  t('.error', messages)
    end
  end

  def final_success_response
    { success: true }
  end
  private
  def build_action(*args)
      action_class.as(self.class.proxy_performer || h.current_user).new(*args)
  end
end

it fails to try to call h.current_user because h is nil.
Any recommendation?

Strategy pattern and implications

TL;DR:

  1. the matcher raise_validation_error does not work with errors from nested actions
  2. I propose adding a new validator like validates :some_nested_action, nested_action: {merge_errors: true}. By merging the errors we'd also circunvent the problems of point 1, but the problem would still persist.
  3. I propose adding a DSL for stratgies

I've used nested actions to implement a strategy pattern, but found some problems that forced me to introduce some terrible monkeypatches.

Consider this example of a strategy pattern implemented using nested actions:

module CommonStuff
  extend ActiveSupport::Concern

  included do
    allow_if { true }

    attribute :soldiers, Array
    attribute :target, String
    attribute :strategy, Object
  end
end

class AssaultPosition < Granite::Action
  include CommonStuff

  validates :strategy_action, nested: true

  def strategy=(val)
    case val
    when Granite::Action then super(val)
    when Symbol
      super({
        flanking: AssaultPosition::Flanking,
        front_assault: AssaultPosition::FrontAssault,
      }.fetch(val))
    end
  end

  private

  def execute_perform!(*)
    strategy_action.perform!
  end

  def strategy_action
    @strategy_action ||= strategy.as(performer).new(attributes)
  end
end

class AssaultPosition::FrontAssault < Granite::Action
  include CommonStuff

  private def execute_perform!(*)
    puts "Performing front assault on #{target}"
  end
end

class AssaultPosition::Flanking < Granite::Action
  include CommonStuff

  validates :soldiers, length: { minimum: 6, message: 'You need more soldiers to perform this strategy' }

  private def execute_perform!(*)
    puts "Flanking #{target}!"
  end
end

action = AssaultPosition.new(strategy: :flanking, soldiers: [1,2,3])
action.valid?
action.errors
=> #<ActiveModel::Errors:0x00007fbffacf2e40
 @base=#<AssaultPosition soldiers: [1, 2, 3], target: nil, strategy: AssaultPosition::Flanking(soldiers: Array, target: String, strategy: Object)>,
 @details={},
 @messages={:"strategy_action.soldiers"=>["You need more soldiers to perform this strategy"]}>

The problem that forces me to monkey patch around is that the provided matcher raise_validation_error relies on error.details, which is not populated in the same manner that with non nested validations. This is most probably related to how the validator works, which actually comes from active_data gem.

The other problem is also more or less of a blocker for using this kind of approach: the messages are namespaced under strategy_action, which may make sense in most situations, but not here, because both the strategies and the parent caller action would accept the same attributes, and the code that must do something with the errors does not expect the error not to be namespaced.

I propose adding our own version of the nested action validator, which accepts the option merge_errors.

In other notes, I think is worth considering adding a DSL for strategies so we could convert my first code example into something like this (with the option to move the implementation of the strategies into another class):

class AssaultPosition < Granite::Action
  allow_if { true }

  attribute :soldiers, Array
  attribute :target, String

  strategy :front_assault do
    private def execute_perform!(*)
      puts "Performing front assault on #{target}"
    end
  end

  strategy :flanking do
    validates :soldiers, length: { minimum: 6, message: 'You need more soldiers to perform this strategy' }

    private def execute_perform!(*)
      puts "Flanking! #{target}"
    end
  end
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.