Git Product home page Git Product logo

mutations's People

Contributors

acook avatar antoniobg avatar aq1018 avatar blambeau avatar cypriss avatar edwinv avatar eliank avatar eugeneius avatar forresty avatar garrettheaver avatar halferty avatar jamesacarr avatar jwoertink avatar khalilovcmd avatar lb avatar lorcan avatar michaelherold avatar mwhatters avatar olleolleolle avatar patrickod avatar petergoldstein avatar rks avatar saverio-kantox avatar seanhealy33 avatar skylerwshaw avatar tomtaylor avatar wlrs 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

mutations's Issues

Adding AR errors

Is it possible to pass an AR record with errors and have the errors be added to the error list?

Something like:

user = User.new(name: "Leo", email: "[email protected]")
unless user.save
  add_errors(user)
end

It'd be nice to have that kind of functionality if it is not possible at the moment.

Questions about form building and recommendations

I'm curious to know what others have done in the way of connecting a form to a mutation. In my scenario, I have a single mutation that accepts a large list of inputs for dealing with all of a record's associations. It accepts canonical names (instead of IDs), and performs lookups of those names while building the parent model. This works fantastically for an API, but I'm struggling to understand how to best implement a form around it.

It's clear that I should not use Rails's form helpers (such as form_for, fields_for) since that would require heavy use of accepts_nested_attributes_for in my models and that would defeat the purpose of encapsulating all of my ActiveRecord interaction into a Mutation. So, the only remaining option would be to use the non-model Rails form tag helpers (or no helpers at all) and then I have no way of handling errors.

Has anyone ventured into the land of building complex forms backed by Mutations in Rails?

Model filter fails after reloading classes

I noticed this when using Mutations with Rails. If I change a file, Rails reloads all the classes. Then Mutations complains that <class> isn't the right class, even though it is, conceptually.

require 'mutations'

class Thing; end
class Mutant < Mutations::Command
  required { model :thing }
end
thing_1 = Thing.new
p Mutant.run(:thing => thing_1).success? # => true

Object.send(:remove_const, 'Thing')
class Thing; end
thing_2 = Thing.new
p Mutant.run(:thing => thing_1).success? # => true
p Mutant.run(:thing => thing_2).success? # => false

Unable to autoload mutations in sub directory from scoped controller

I have a controller inside of app/controllers/api/guides_controller.rb (it is namespaced as Api::GuidesController).

I have a mutation within app/mutations/guides/guide_update.rb as GuideUpdate (not namespaced).

When I try calling run() on the mutation I get an error for "uninitialized constant Api::GuidesController::GuideUpdate".

The quick fix for now was to make a mutations.rb file inside of initializers that does this:

Dir["app/mutations/**/*.rb"].each{|file| load file}

but this is probably a hack. Did I fail to follow a naming convention or something? It's for an opensource project, so feel free to take a look at the source code.

Thanks for maintaining this gem, it's one of the best I've seen in a while.

Handling of non-permitted inputs

It would great to be able to handle cases where unpermitted inputs were passed into the mutation; resulting in invalidation. Does anyone else see this as a useful addition to Mutations?

Hashes with arbitrary structure

Is it currently possible to describe a hash param allowing arbitrary hash structure? I need to perform pretty complex validations on this param. Having just hash :h without block just return empty hash.

Polymorphic mutations

I really like the mutations approach to little service objects, so thank you. I was wondering about the best way to implement something that's a bit like a polymorphic object.

I'd like to make a CreateFooMutation, that checks a type string parameter, and depending on the contents, defers to a Create{type}FooMutation for futher validation checking and execution, before returning the outcome back to the original caller.

I can see that I'd want to pass the raw inputs to the second mutation on initialization, but what's the best way to merge all the results back in, including the validations, as if they'd been performed on the first mutation?

Command current user awareness

Another feature that I've implemented, that I think would be very helpful for users of the mutations gem is current user awareness.

Below is a somewhat contrived example

class CreateBook < Mutations::Command
  required do
     title :string
  end

  def execute
    model_scope.create(title: title)
  end

  protected

    def model_scope
      current_user.present? ? current_user.books : Book
    end
end

would be called via:

def create
  CreateBook.as(current_user).run(params[:book])
end

If you would be interested in supporting this kind of api, I can speak to a number of great benefits over the approach of passing current user as an input.

Let me know and I will prepare a pull request

Documentation for wildcard hash filter

Hi,

I needed to write a hash attribute that was a dynamic key/value lookup.

After examining the source, I noticed that you can do wildcard filters, but the feature was not documented.

I've added documentation to the wiki. Apologies if I have misunderstood the feature. I am raising this issue in hopes that someone will proof read my work.

Keep up the great work on this project.

Optional values in required hashes aren't optional

I've a Mutations::Command subclass that starts out like

class CreateAndPublishBlogPost < Mutations::Command
  required do
    duck :blog, methods: [:new_post]
    hash :post_params do
      required do
        string :title
      end
      # listed as optional, but one or the other IS required; see #validate
      optional do
        string :body
        string :image_url
      end
    end
  end

Within the (required) :post_params hash, only :title is required; both :body and :image_url are optional (ignoring the comment about #validate for now; we never get that far).

Here's a failing scenario:

The inputs passed in are

{
  blog: # SomeObjectInstance; passes
  post_params: {
    title: 'title' # validates
    body: 'body' # validates
  }
}

Validation fails when no :image_url value is found; the outcome.errors object passed back up to the controller contains

[1] pry(#<Blog::PostsController>)> outcome.errors
=> {:post_params=>
  {:image_url=>
    #<Mutations::ErrorAtom:0x007fc89af206d8
     @index=nil,
     @key=:image_url,
     @message=nil,
     @symbol=:empty>}}

which leads to

[2] pry(#<Blog::PostsController>)> outcome.errors[:post_params][:image_url].message
=> "Image Url can't be blank"

Why can't it be?

Enable composing in addition to inheriting

I saw your library pop up on my Prismatic feed today and I like it! Thanks for sharing it!

As I was reading the README, I realized all your examples use inheritance. So, I copied your implementation of Mutations::Command to a module and added an included method and it can now be used as a module include instead of forcing subclass. Is this something you'd like to have or do you prefer the forced inheritance method? I have specs and everything so if you're interested, I'll submit a PR.

P.S. your specs run laser fast, it's ridiculously fun to run them.

Does Mutations need Maintainers?

@cypriss I'm sure you're a very busy dude... do you need help maintaining this project?

There are growing number of pull requests and other issues that some people would love to be able to help get merged into the main branch.

Any chance of that happening?

Inline filter documentation

I'm working on my own library which subclasses mutations, and I am writing this to see if you would have any interest in merging this particular feature into the mutations gem itself. This will determine some aspects of my approach going forward

class UpdateBook < Mutations::Command
  required do
    desc "The title of the book"
    string :title

    optional do
       desc "Tags for the book"
       array :tags
    end
  end
end

This gives anyone the ability to generate documentation for the command's interface:

UpdateBook.interface_documentation

This produces (still a WIP)

{
  required_inputs: ["name"],
  optional_inputs: ["tags"],
  schema: {
    title: {
      type: "string",
      description: "The title of the book"
    },
    tags: {
      type: "array",
      description: "Tags for the book"
    }
  }
}

I have considered using just a description key in the options hash but think that this is not the optimal style of writing these kinds of things imo and it isn't inline with the rest of the conventions in my library

About the DSL

Validating hashes with required and optional blocks is a bit strange. Example:

# make street2 of address optional

class PropertyCreate < Mutations::Command
  required do
    string :name
    hash :address do
     required do
        string :street1
        string :city
        string :state
        string :zip
      end
    end
  end

  optional do
    hash :address do
      optional do
        string :street2
      end
    end
  end
end

I couldn't get this to work any other way. I have a suggestion for an alternative DSL syntax like this:

class PropertyCreate < Mutations::Command
  inputs do
    string :name
    hash :address do
      string :street1
      string :street2, optional: true
      string :city
      string :state
      string :zip
    end
  end

So basically drop the optional and required block, and add an extra option to mark the field optional?

Yeah, but what does it do better?

The syntax is sort of pleasant, but I don't understand what this library does better than ActiveRecord validations and life-cycle hooks.

Status codes in errors

It'd be nice to be able to specify specific http status codes in the errors in order to return better responses.

unless outcome.success?
  render json: outcome.errors.symbolic, status: outcome.errors.status
end

Any thoughts on this? Something like outcome.errors.status or outcome.errors.code

Mutations calling other mutations

This is more of a question than an issue, but I'm looking to confirm a good pattern for solving a particular use-case demonstrated with faux example below:

Let's say we have these mutations

# mutations/posts/create.rb
CreatePost
  # Adds a new post
  # Optionally can supply a comment and attachment

# mutations/posts/comment.rb
PostComment
  # Adds a comment to a given post
  # Contains additional business-logic, such as email notifications

# mutations/posts/attachment.rb
PostAttachment
  # Adds an attachment to a given post
  # Contains additional business-logic, such as email notifications

In the scenario above, the CreatePost can optionally be passed inputs to support the creation of a comment and an attachment.

We also need PostComment and PostAttachment mutations isolated because they can be called from other controllers (API endpoints)

Instead of duplicating the logic for interacting with the models in all three mutations and thus having to use callbacks for the business-logic, it would make sense to have the PostComment and PostAttachment mutations each contain the needed business logic- and thus have the CreatePost mutation call the other 2 mutations, when necessary.

class CreateComment
  def execute()
    @post = Post.create!(inputs)
    PostComment.run!(@post, ...) if inputs[:comment]
    PostAttachment.run!(@post, ...) if inputs[:attachment]
  end
end

Questions about this approach:

  1. What's the best/recommended way to deal with transactions? If PostComment or PostAttachment fails with an error, we need to rollback the entire transaction. I presume it would just be a matter of wrapping the subsequent mutation calls in a transaction.
class CreatePost
  def execute()
    ActiveRecord::Base.transaction do
      @post = Post.create!(inputs)
      PostComment.run!(@post, ...) if inputs[:comment]
      PostAttachment.run!(@post, ...) if inputs[:attachment]
    end
  end
end
  1. In the case of PostAttachment mutation failing, we need to ensure the business logic in PostComment never gets called. How would the PostComment mutation ever know though? It only tracks the success of it's own individual outcome.

So in general, what is the best practice for using Mutations in this kind of scenario? (mutations calling other mutations as part of a larger transaction)

Offering help with maintainership

Hey there!

Myself and my colleagues at Intercom are big fans and users of this gem, and wanted to thank you and all the contributors for your work.

In the last few months we've built some monkeypatchsolutions to extend mutations in various ways, some of which we've opened as PRs: #90, #89.

These and other changes live in our codebase as monkeypatches. We think they'd be beneficial to the wider community and we'd love to help get them merged into master and released. On top of that we'd like to offer our communal helping hands in maintaining and otherwise improving the gem.

So I wanted to open an issue to start a discussion about this, and to see if you would be open to some help in maintaining the codebase ๐Ÿ˜„

Namespacing Issues

Hi there. I'm trying to design a bigger rails3 application, using mutations to abstract away my business logic. Basically i have a "main" app, including only models and mutations, and many rails engines, which only include the views/view-logic and slim controllers which use the mutations from the "main" app to get/update data.

To get everything sorted in a sane way, i namespaced my mutations. For example, there is a model Category. I have a mutation which returns the root of a Category tree, under the namespace Core::Category::GetRoot. Given that the "root" of my tree structure is always the first record, the mutation only looks as follows:

class Core::Category::GetRoot < Mutations::Command
def execute
Category.first
end
end

A Unittest in my main-app says everything is okay and it works. But in my Engine-Controller, which uses just Core::Category::GetRoot.run!, i get the following error:

uninitialized constant Core::Category::GetRoot::Category

with a single line stacktrace, pointing to the only line within the execute method.

It seems for me, that the namespace-resolution just fails at some point. Category is a usual model, not a class within my mutation module, so is there any lookup-mechanic not working or something like this? Or did i something really wrong?

PS: i use Rails 3.2.13 with Ruby 2.0, and Mongoid as my ODM if this matters.

How do I accept arbitrary hash data?

Not an issue per-se, but I would like to do something like:

required do
integer :page_id
hash :user_defined_page_data
end

Where user_defined_page_data is a hash provided by the end user.

Thank you,

How to combine mutation errors from a mutation?

In our application, we have a scenario that requires our top-level mutation object to call another mutation, and to be able to "bubble up" the errors of the lower-level mutation to the top-level mutation's caller. I'm wondering what the best way is to approach this. My first instinct is to simply do this in the top-level mutation execute method:

outcome = LowerLevel::Update.run(params)
unless outcome.success?
  errors.merge(outcome.errors)
end

Should the errors be merged like this? Is there a better approach to "bubbling up"?

Getting around model filters

I have a bunch of mutations that use model filters, like this one:

class UpdateEmailAddress < Mutations::Command
  required do
    model  :user, new_records: true
    string :email
  end

  def execute
    user.email = email
    user.save
  end
end

I want to be able to test them using double()s such as what RSpec provides. Unfortunately, they fail the :is_a? test, and so the mutation never runs.

My current (kludgy) workaround is to do this:

class MockUser
  def is_a?(klazz)
    (klazz == User) ? true : super
  end

  attr_accessor :email

  def save
    true
  end
end

describe UpdateEmailAddress do
  it { should be_a Mutations::Command }

  let(:user)        { MockUser.new }
  let(:new_address) { '[email protected]' }
  let(:valid_input) {{ user: user, email: new_address }}

  context 'all input correct' do
    subject { described_class.run(valid_input) }

    it { should be_success }

    it 'sets the user\'s email and saves' do
      user.should_receive(:save)
      subject
      user.email.should eq(new_address)
    end

  #snip...
  end
end

Is there a better way I've missed?

Integer filter accepts non-numeric strings.

Eg. '123foo' gets converted to 123, due to the use of to_i. I think that it would be preferable for the integer filter to do something like:

begin
  data = Integer(data)
rescue ArgumentError
  return [data, :integer]
end

Let me know what you think, I can cut a PR if you want.

CI failing for RBX

Every pull request seems to fail only because the CI service is having issues with RBX. Any chance someone can have this resolved so our PRs can be reviewed on their merits?

Sharing inputs across multiple mutations?

The FAQ mentions that the best way to share code between multiple Mutations is to include the common module. In my application, we have 2 API versions (namespaces API::V1 and API::V2). Each controller pushes the params into the respective Mutation's run() method. It is a large set of params, with only a small handful of input differences between the 2 APIs. Ideally, to stay DRY, I would like to share the common set of required/optional inputs, so that both mutation objects do not have to define them more than once.

Each mutation would then be responsible for the processing of specific inputs, and call upon a shared module for the common input processing.

I haven't taken a stab at this yet, but wanted to run the concept by this group to get some opinions on the approach.

Classifying types of errors

Currently, a mutation can either succeed or not. If it does not, it will have a list of errors. The errors can in theory be whatever you want, but the current implementation strongly suggests field-related errors.

I'm working on putting some mutations in place in an existing Rails app. This mostly entails refactoring out some logic from models, but mostly extracting code "trapped" in controllers. Part of this is controller filters - if the mutations are to be used in other contexts, I need to extract those too. Thus I end up with a mutation that really can end a few ways:

  • Succeeds - all is good
  • Fails for missing inputs - render the form again
  • Fails - some precondition is not met (e.g. user is not logged in) - should redirect

In order to treat different classes of errors differently, it would be nice to be able to recognize them on the outside without having to loop through the error list.

Have you put any thought into this? I don't have any solutions, but I'm happy to join in on a discussion, just wanted to see if you'd thought about it before suggesting anything.

File filter

Is there a suggested way of handling file uploads?

At the moment, Rails will pass a ActionDispatch::Http::UploadedFile through as params[:foo] in a controller. We'd also like the ability to accept a Tempfile from other business logic we have, and an actual File from our test suite.

They all behave like aFile, but with some additional methods and behaviour. They all have .size and .read and so on.

This is sort of related to #14, but is it worth adding a filter that accepts things that behave like a File object?

Specify a default for optional values?

It would be really nice to be able to specify a default for optional values within the DSL. Here's an example:

class UserCreator < Mutations::Command
  required do
    string :email
    string :name
  end

  optional do
    string :background_color
  end

  def execute
    user = 
      if background_color_present?
        User.create!(inputs)
      else
        User.create!(inputs.merge({background_color: ['red', 'green', 'blue'].sample}))
      end
    user
  end
end

Would become:

class UserCreator < Mutations::Command
  required do
    string :email
    string :name
  end

  optional do
    string :background_color, default_value: ['red', 'green', 'blue'].sample
  end

  def execute
    User.create!(inputs)
  end
end

If this seems like a reasonable addition, I'd be happy to take a stab at implementing it. Thanks!

Validating optional non-nil fields

I have a case where a field can be specified optionally, and should never allow a nil value. I expected to simply declare:

optional do
    string :state
end

(Intentionally omitted specifying :nils=>false since that is the default)

However, when I send "state"=>nil, the state input is silently filtered out. Shouldn't this have raised a validation error instead?

That would help in clarifying to the user that this field cannot be unset.

Mutations with forms in Rails (2.3)

This isn't an issue or a question. This describes how I have integrated Mutations into form views within a Rails 2.3 app. This could be a discussion on how Mutations could evolve to have wider uses within Rails, or it could just be a reference for others looking to implement their own similar solution. This is a bit of an experiment, but comments & feedback are welcome!

This is what my controller actions look like:

def new
  @mutation = CreateMutationClass.new
end

def create
  @mutation = CreateMutationClass.new(params[:mutation])
  outcome = @mutation.run
  if outcome.success?
    flash[:notice] = "Success"
    redirect_to some_path
  else
    @errors = outcome.errors.message_list
    render :action => :new
  end
end

def edit
  @mutation = UpdateMutationClass.new(MyModel.find(params[:id]))
end

def update
  @mutation = UpdateMutationClass.new(MyModel.find(params[:id]), params[:mutation])
  outcome = @mutation.run
  if outcome.success?
    flash[:notice] = "Success"
    redirect_to some_path
  else
    @errors = outcome.errors.message_list
    render :action => :edit
  end
end

Note that on the mutation class I initially call new rather than run; this allows me to bind the Rails forms to the mutation rather than an ActiveRecord model. I am also passing in an ActiveRecord model as the first parameter on the edit, and update actions, which assigns attributes to the mutation straight from the model (more on that below).

In my views I then use form_for @mutation, :url => some_path (the :url is required here, although I suspect it would be possible to extend the mutation class to allow Rails to deduce the url).

My mutation classes are mostly unchanged, but with a few additions. Here's an example of the CreateMutationClass.

class CreateMutationClass < Mutations::Command
  include MutationExtensions::Base

  use :new_record_mocks
  use :model_name

  required do
    # ...
  end

  optional do
    # ...
  end

  def execute
    # ...
  end

end

The required & optional blocks, and the execute method, are all standard as per the mutations docs.

And the UpdateMutationClass:

class UpdateMutationClass < Mutations::Command
  include MutationExtensions::Base

  use :existing_record_mocks
  use :model_name
  use :base_model, :my_model

  map(:some_attr) { |model| model.method_to_determine_some_attr }
  map(:another_attr) { Date.today }

  required do
    # ...
  end

  optional do
    # ...
  end

  def execute
    # ...
  end

end

use :new_record_mocks and use :existing_record_mocks each set up the following methods: id, to_param, new_record? and _destroy. These allow Rails to render the form properly.

use :model_name simply changes the input field base name, defaulting to 'mutation'.

use :base_model allows linking the mutation to an ActiveRecord model. This redefines the new method to allow the first parameter to be an ActiveRecord model of the expected class. By default the mutation attributes will attempt to get their value by calling a method of the same name on the model, however using a base model also allows using the map method to handle non-matching attributes. Passing in multiple parameters still works the same way (subsequent hash parameters will override existing attributes).

And here's the MutationExtensions module:

module MutationExtensions
  module Base
    def self.included(base)
      base.class_eval do
        def self.use(*args)
          case args.shift
          when :model_name
            @model_name = ::ActiveSupport::ModelName.new(args.first || 'mutation')
          when :base_model
            @base_model = args.first
            self.send(:extend, BaseModel)
            self.send(:required) do
              model(*args)
            end
          when :new_record_mocks
            self.send(:include, Mock::NewRecord)
          when :existing_record_mocks
            self.send(:include, Mock::ExistingRecord)
          end
        end
      end
    end
  end

  # Allows linking the mutation class to a model, so that
  # new mutation instances can be instantiated by passing
  # in a model instance as the first parameter
  module BaseModel
    def base_model
      @base_model
    end

    def mappings
      @mappings ||= {}.with_indifferent_access
    end

    def map(attribute, &block)
      mappings[attribute] = block
    end

    def new(*args)
      if args.first.class.model_name.underscore.to_sym == @base_model
        record = args.shift

        inputs_keys = input_filters.required_inputs.keys | input_filters.optional_inputs.keys
        attrs = inputs_keys.inject({}) do |hash, key|
          if proc = mappings[key]
            hash[key] = proc.call(record)
          elsif record.respond_to?(key)
            hash[key] = record.send(key)
          end
          hash
        end

        attrs[@base_model] = record
        args.unshift(attrs)
      end

      super(*args)
    end

    def association_reflection(association_name, associated_class)
      @association_reflections ||= {}
      @association_reflections[association_name.to_sym] = associated_class
    end

    def reflect_on_association(association_name)
      OpenStruct.new(:klass => @association_reflections[association_name.to_sym])
    end
  end

  # Mocks new or existing records, so that `form_for` renders ok
  module Mock
    module AnyRecord
      def _destroy; nil; end
      def to_param; id ? id.to_s : id; end
    end

    module NewRecord
      include Mock::AnyRecord
      def new_record?; true; end
      def id; nil; end
    end

    module ExistingRecord
      include Mock::AnyRecord
      def new_record?; false; end

      def id
        if base_model = self.class.base_model
          self.send(base_model).try(:id) || 0
        else
          0
        end
      end
    end
  end
end

@cypriss, I'd be interested to hear your thoughts on moving towards wider Rails support so that mutations can more easily apply to UI apps as well as APIs. I'm aware that there are other similar gems out there which integrate with Rails forms, but I've so far enjoyed working with mutations the most :-)

How do you use mutations for updating records?

Assuming typical params hash when updating:

{
  id: 77,
  auction: {
    title: 'one two three',
    price: 77.9
  }
}

Because run() only accepts hashes, i cannot handle finding record in the controller.
So mutation gets auction id and attributes and is be responsible for finding proper record to update and updating id.

class AuctionUpdate < Mutations::Command
  required do
    integer :id
    hash :auction do
      string :title
      float :price
    end
  end

  def execute
    record = Auction.find(id)
    record.attributes = auction
    record.save!
    # some other logic
    record
  end
end

Am i missing something? How do you do this?

Dynamic filter / ducktyping

hi
im trying to decouple my future rails apps from my business logic and i think this gem could be a bit of help in this.
now im writing a test

describe FindUnreadTicketsForUser do
  it "finds unread tickets for a user" do
    user = double('user', id: 3)

    repo = double('repo')
    repo.should_receive(:find).with({is_read: false, assigned_to_user_id: user.id}).and_return(['?'])

    unread_tickets = FindUnreadTicketsForUser.run! user: user, tickets_repo: repo

    unread_tickets.length.should eq(1)
  end
end

and the command

class FindUnreadTicketsForUser < Mutations::Command
  required do
    model :tickets_repo
    model :user
  end

  def execute
    tickets_repo.find....
  end
end

this is obviously not finished and maybe my testing approach is wrong. but i dont want to be bound to a specific repo or user class. im just testing the command, not the repo. actually i was not able to have any required 'field' that has a dynamic class. i dont want to say that the field user MUST BE kind of User.

im not sure what im doing here currently, but i know that your gem is missing a DynamicFilter or something like this (or please tell me how to do this ;))

[HowTo] cancel execute method gracefully

Hi there, i have the following scenario:

  1. i have an mutation.

  2. parameter checks say: everything okay.

  3. i start processing the execute method.

  4. i notice an logical error at the beginning of my business logic.

  5. [ ??? ]

    outcome.success? # => should be false!
    outcome.errors # => directly add a textmessage?

So, i want to "return" directly from the execute(), setting success (and ideally a meaningful error) correctly.

What is the preferred way to achieve this neat behaviour? Usage of Mutations is within a Rails 3.2 App on MRI 2.0 if this matters.

Bump version to 0.7.3 ?

I am using the latest version from rubygems but it seems it doesn't include the latest fixes (ie: #86). When do you think it would be possible to have a new version of this gem?

_present? and = acting weird

I'm having some issues with mutations and the usage of _present? in conjunction of =

Here is my code :

class Shifts::Create < Mutations::Command

  required do
    integer :store_id
    model :job
  end

  optional do
    integer :reward_points
  end


  def execute
    Rails.logger.info "#{reward_points_present?} #{reward_points}"

    if !reward_points_present?
      Rails.logger.info "#{reward_points_present?} #{reward_points}"
      reward_points = job.base_reward_points
      Rails.logger.info "WHAT ?"
      Rails.logger.info "#{reward_points_present?} #{reward_points}"
    end

    Rails.logger.info "#{reward_points_present?} #{reward_points}"

  end

end

Running

Shifts::Create.run!(job: job, store_id: store.id)
would legitimately give me back

false
false
WHAT ?
false 50
false 50

But when I run Shifts::Create.run!(job: job, store_id: store.id, reward_points: 12)

I am unexpectedly getting :

true 12
true

I have spent hours trying to understand but I finally think this is not the expected behavior, is it ?

Line error in exceptions

Hello,

I've using mutations for almost three months now and there is something that is kind of a recurring pain and I don't know if I'm doing something wrong here. When there is an exception in a mutation debugging the backtrace is kind of tricky. The line where the exception occurs is in the mutations code instead of the execute where it would be easier for me to debug it:

/app/vendor/bundle/ruby/2.0.0/gems/mutations-0.5.12/lib/mutations/command.rb:84:in `run!'

/app/vendor/bundle/ruby/2.0.0/gems/mutations-0.5.12/lib/mutations/command.rb:36:in  `run!' 

/app/app/jobs/update_site_google_analytics_job.rb:16:in `block in perform' 

/app/vendor/bundle/ruby/2.0.0/gems/exceptional-2.0.33/lib/exceptional.rb:53:in `call' 

/app/vendor/bundle/ruby/2.0.0/gems/exceptional-2.0.33/lib/exceptional.rb:53:in `rescue_and_reraise' 

Is there a workaround for this?

Thanks in advance for your help :)

Multiple errors for parameters

It looks like at the moment it's only possible for one error to exist on each parameter, but it seems like there are many situations where there might be two or more errors at one time.

Is it worth moving to a structure where each parameter can have an array of errors?

Boolean filter does not support value check

I need to validate the value of a boolean before performing a mutation command.

I would like to return all the basic validation messages at once. For example if the username or password is missing and the terms and conditions have not been accepted I would like to return both messages at once.

Unfortunately the BooleanFilter only validates the presence of a boolean value, not that it is either true or `false.

I was able to check that the value is true using this method:

class SignUpUser < Mutations::Command
  required do
    string :email
    string :password
    model :accept_terms_and_conditions, class: TrueClass
  end
end

Obviously using the ModelFilter and expecting it to be an (or more accurately, the) instance of TrueClass is not an expressive solution to my requirement.

I plan to propose a PR that allows me to express that the boolean should be true,

module Mutations
  class SignUpUser < Mutations::Command
    required do
      string :email
      string :password
      boolean :accept_terms_and_conditions, expect: true
    end

    def execute
      # do stuff
    end
  end
end

'model_id' gets passed as 'model', which clashes with similarly named hash.

I have a mutation that updates a stage, and is defined as requiring a user, a stage_id (to identify a stage to update), and a hash that contains the optional fields to update.

   required do
      model :user
      string :stage_id

      hash :stage do
        optional do
          string :name
          array :environment
          array :soil
          array :light
          integer :stage_length
        end
      end
    end

When I run the mutation in tests to determine whether an empty mutation returns the right errors: mutation.run({}).errors.message_list

I'm getting these errors:

["User is required", "Stage is required", "Stage is required"]

This seems to be having as effect that when I pass it an id through stage_id as a string, and a hash through stage to update, it will say "Stage is not a string"

When I change the name of stage_id to just id it recognizes it as a required id. Is this a feature that I just don't understand? (I'm pretty new to Ruby on Rails).

readme - how are exceptions handled

Please add to readme:

How are exceptions handled inside the execute method?
Do they bubble up to the consumer? Or are they catched and your added to the errors?

The mutation name in `ValidationException`

Imagine the following code:

begin
  MutationA.run! params
  MutationB.run! params
rescue ValidationException => e
  ...
end

It would be really fine to have a quick access to the cause of the error, like a mutation name. At the moment, everything we got there is the Outcome instance, wrapped with ValidationException.

In fact, the back-reference to the mutation might be useful in many other cases.

No CHANGELOG?

As the number of watchers grow and public API gets more precise, it could be fine to start a CHANGELOG so as to let people know when changes will potentially hurt.

HashWithIndifferentAccess causing unexpected behavior

When using a ModelFilter with an OmniAuth::AuthHash (from the omniauth gem) as the expected class, the Mutation::Command fails because the auth hash is cast to a HashWithIndifferentAccess.

You can see some examples in this repo. Here's a short example inline as well:

class OmniAuthAuthentication < Mutations::Command
  required do
    model :auth_hash, class: OmniAuth::AuthHash
  end

  def execute
    # Never runs because auth_hash fails the type check
    # auth_hash is cast to a HashWithIndifferentAccess
  end
end

I've started work on a fix that casts all input names to symbols, but it causes a lot of specs to need some tweaks and would not be backwards-compatible. Would such a solution be adequate? It seems like there is not really a need for HashWithIndifferentAccess, since it's overkill for the use that it's being put to.

Error message issue

This gem should support add_error_list where I can just pass model.errors.full_messages. Basically add a simple string error message vs forcing to use symbols only.

def execute
  if user.valid?
    user.save
    # Do other stuff - send emails, newsletters
    user
  else
    add_error_list(user.errors.full_messages)
  end
end

Right now it seems you have to use add_error with individual symbol etc.

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.