Coder Social home page Coder Social logo

reform-rails's Introduction

Trailblazer

Battle-tested Ruby framework to help structuring your business logic.

Gem Version

What's Trailblazer?

Trailblazer introduces new abstraction layers into Ruby applications to help you structure your business logic.

It ships with our canonical "service object" implementation called operation, many conventions, gems for testing, Rails support, optional form objects and much more.

Should I use Trailblazer?

Give us a chance if you say "yes" to this!

  • You hate messy controller code but don't know where to put it?
  • Moving business code into the "fat model" gives you nightmares?
  • "Service objects" are great?
  • Anyhow, you're tired of 12 different "service object" implementations throughout your app?
  • You keep asking for additional layers such as forms, policies, decorators?

Yes? Then we got a well-seasoned framework for you: Trailblazer.

Here are the main concepts.

Operation

The operation encapsulates business logic and is the heart of the Trailblazer architecture.

An operation is not just a monolithic replacement for your business code. It's a simple orchestrator between the form objects, models, your business code and all other layers needed to get the job done.

# app/concepts/song/operation/create.rb
module Song::Operation
  class Create < Trailblazer::Operation
    step :create_model
    step :validate
    left :handle_errors
    step :notify

    def create_model(ctx, **)
      # do whatever you feel like.
      ctx[:model] = Song.new
    end

    def validate(ctx, params:, **)
      # ..
    end
    # ...
  end
end

The step DSL takes away the pain of flow control and error handling. You focus on what happens: creating models, validating data, sending out notifications.

Control flow

The operation takes care when things happen: the flow control. Internally, this works as depicted in this beautiful diagram.

Flow diagram of a typical operation.

The best part: the only way to invoke this operation is Operation.call. The single entry-point saves programmers from shenanigans with instances and internal state - it's proven to be an almost bullet-proof concept in the past 10 years.

result = Song::Operation::Create.(params: {title: "Hear Us Out", band: "Rancid"})

result.success? #=> true
result[:model]  #=> #<Song title="Hear Us Out" ...>

Data, computed values, statuses or models from within the operation run are exposed through the result object.

Operations can be nested, use composition and inheritance patterns, provide variable mapping around each step, support dependency injection, and save you from reinventing the wheel - over and over, again.

Leveraging those functional mechanics, operations encourage a high degree of encapsulation while giving you all the conventions and tools for free (except for a bit of a learning curve).

Tracing

In the past years, we learnt from some old mistakes and improved developer experience. As a starter, check out our built-in tracing!

result = Song::Operation::Create.wtf?(params: {title: "", band: "Rancid"})

Tracing the internal flow of an operation.

Within a second you know which step failed - a thing that might seem trivial, but when things grow and a deeply nested step in an iteration fails, you will start loving #wtf?! It has saved us days of debugging.

We even provide a visual debugger to inspect traces on the webs.

There's a lot more

All our abstraction layers such as operations, form objects, view components, test gems and much more are used in hundreds of OSS projects and commercial applications in the Ruby world.

We provide a visual debugger, a BPMN editor for long-running business processes, thorough documentation and a growing list of onboarding videos (TRAILBLAZER TALES).

Trailblazer is both used for refactoring legacy apps (we support Ruby 2.5+) and helps big teams organizing, structuring and debugging modern, growing (Rails) applications.

Documentation

Make sure to check out the new beginner's guide to learning Trailblazer. The new book discusses all aspects in a step-wise approach you need to understand Trailblazer's mechanics and design ideas.

The new begginer's guide.

reform-rails's People

Contributors

adis-io avatar apotonick avatar chrkau avatar emaglio avatar fernandes avatar fran-worley avatar jcoyne avatar marcelolx avatar mariannesiren avatar meganemura avatar pat avatar richardboehme avatar rickenharp avatar samstickland avatar seuros avatar sliminas avatar tilod avatar y-yagi avatar yogeshjain999 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reform-rails's Issues

Still issues with property named 'format'

I'm not sure what's changed between Ruby versions but...

This change resolves the problem on Ruby 2.2.3:
trailblazer/reform@14c4fa2

A format method is required to fix the issue on Ruby 2.0.0
trailblazer/reform@4f418d0

And something different is needed for Ruby 2.3.1. Interestingly the raise is occurring in a different location than on 2.0 somewhere embedded in representable.

Can confirm that the issue does not occur in Reform with dry-v so it must be something to do with AR/AM-V.

I've skipped the test for now but can't see me devoting much time to fixing it so please PR if you think you can resolve it!

undefined method `persisted?' (and `to_key`) for #<Hash> when using Composition and `form_for`

Without Composition, persisted? works fine, but with Composition it gives this error apparently because it delegates to its model which is a Hash of multiple models.

Steps to reproduce

class MyForm < Reform::Form
  include Composition
  model :a

  property :prop_1, on: :a
  property :prop_2, on: :b
end

form = MyForm.new(a: OpenStruct.new, b: OpenStruct.new)
form.persisted?  # undefined method `persisted?' for {:a=>#<OpenStruct>, :b=>#<OpenStruct>}:Hash

Using it in a form with form_for @form also generates the error, since form_for calls persisted?.

Workaround

An easy workaround is to simply add this to form class:

  def persisted?
    false
  end

unique validator - persisted record allow count issue

I have an existing record a, with a name attr on the model of 'foo' and a persisted record being validated, b, and b's name is being changed to 'foo'.

When i run the validation against b, the unique validation passes, because it is persisted, the allow count is set to 1, and the query count is also 1.

Should not the allow count be set to 1, only if b is included in the query?

Have patched this locally, so happy to supply a PR just wanted to confirm this case first.

0.1.4 makes eager loading fail

I tried to update from 0.1.2 > 0.1.4
reform 2.2.1

Code:

require "reform"
require "reform/rails"

class SomeForm < Reform::Form
     include Reform::Form::ActiveModel::Validations

     # property...
end

Got the same error with or without include

Error Log

/users/pikachuexe/projects/spacious/spacious-rails/app/controllers/api/lister_app/v1/listings/create_controller.rb:107: warning: toplevel constant ActiveModel referenced by Reform::Form::ActiveModel
/users/pikachuexe/projects/spacious/spacious-rails/app/controllers/api/lister_app/v1/listings/update_controller.rb:139: warning: toplevel constant ActiveModel referenced by Reform::Form::ActiveModel
/users/pikachuexe/projects/spacious/spacious-rails/app/controllers/api/lister_app/v1/my/lister_profile/update_controller.rb:148: warning: toplevel constant ActiveModel referenced by Reform::Form::ActiveModel
/users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/reform-2.2.1/lib/reform/validation.rb:6:in `validation_groups': undefined local variable or method `validation_group_class' for Api::ListerApp::V1::PropertyImages::CreateController::Form:Class (NameError)
Did you mean?  validation_groups
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/reform-2.2.1/lib/reform/validation.rb:13:in `validation'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/reform-2.2.1/lib/reform/validation.rb:19:in `validates'
    from /users/pikachuexe/projects/spacious/spacious-rails/app/controllers/api/lister_app/v1/property_images/create_controller.rb:138:in `<class:Form>'
    from /users/pikachuexe/projects/spacious/spacious-rails/app/controllers/api/lister_app/v1/property_images/create_controller.rb:134:in `<class:CreateController>'
    from /users/pikachuexe/projects/spacious/spacious-rails/app/controllers/api/lister_app/v1/property_images/create_controller.rb:9:in `<module:PropertyImages>'
    from /users/pikachuexe/projects/spacious/spacious-rails/app/controllers/api/lister_app/v1/property_images/create_controller.rb:8:in `<module:V1>'
    from /users/pikachuexe/projects/spacious/spacious-rails/app/controllers/api/lister_app/v1/property_images/create_controller.rb:7:in `<module:ListerApp>'
    from /users/pikachuexe/projects/spacious/spacious-rails/app/controllers/api/lister_app/v1/property_images/create_controller.rb:6:in `<module:Api>'
    from /users/pikachuexe/projects/spacious/spacious-rails/app/controllers/api/lister_app/v1/property_images/create_controller.rb:5:in `<top (required)>'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/activesupport-4.1.15/lib/active_support/dependencies.rb:247:in `require'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/activesupport-4.1.15/lib/active_support/dependencies.rb:247:in `block in require'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/activesupport-4.1.15/lib/active_support/dependencies.rb:232:in `load_dependency'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/activesupport-4.1.15/lib/active_support/dependencies.rb:247:in `require'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/activesupport-4.1.15/lib/active_support/dependencies.rb:348:in `require_or_load'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/activesupport-4.1.15/lib/active_support/dependencies.rb:307:in `depend_on'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/activesupport-4.1.15/lib/active_support/dependencies.rb:225:in `require_dependency'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/engine.rb:468:in `block (2 levels) in eager_load!'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/engine.rb:467:in `each'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/engine.rb:467:in `block in eager_load!'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/engine.rb:465:in `each'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/engine.rb:465:in `eager_load!'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/engine.rb:346:in `eager_load!'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/application/finisher.rb:58:in `each'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/application/finisher.rb:58:in `block in <module:Finisher>'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/initializable.rb:30:in `instance_exec'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/initializable.rb:30:in `run'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/initializable.rb:55:in `block in run_initializers'
    from /Users/PikachuEXE/.rvm/rubies/ruby-2.3.1/lib/ruby/2.3.0/tsort.rb:228:in `block in tsort_each'
    from /Users/PikachuEXE/.rvm/rubies/ruby-2.3.1/lib/ruby/2.3.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
    from /Users/PikachuEXE/.rvm/rubies/ruby-2.3.1/lib/ruby/2.3.0/tsort.rb:431:in `each_strongly_connected_component_from'
    from /Users/PikachuEXE/.rvm/rubies/ruby-2.3.1/lib/ruby/2.3.0/tsort.rb:349:in `block in each_strongly_connected_component'
    from /Users/PikachuEXE/.rvm/rubies/ruby-2.3.1/lib/ruby/2.3.0/tsort.rb:347:in `each'
    from /Users/PikachuEXE/.rvm/rubies/ruby-2.3.1/lib/ruby/2.3.0/tsort.rb:347:in `call'
    from /Users/PikachuEXE/.rvm/rubies/ruby-2.3.1/lib/ruby/2.3.0/tsort.rb:347:in `each_strongly_connected_component'
    from /Users/PikachuEXE/.rvm/rubies/ruby-2.3.1/lib/ruby/2.3.0/tsort.rb:226:in `tsort_each'
    from /Users/PikachuEXE/.rvm/rubies/ruby-2.3.1/lib/ruby/2.3.0/tsort.rb:205:in `tsort_each'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/initializable.rb:54:in `run_initializers'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/railties-4.1.15/lib/rails/application.rb:300:in `initialize!'
    from /users/pikachuexe/projects/spacious/spacious-rails/config/environment.rb:5:in `<top (required)>'
    from /users/pikachuexe/projects/spacious/spacious-rails/spec/spec_helper.rb:13:in `require'
    from /users/pikachuexe/projects/spacious/spacious-rails/spec/spec_helper.rb:13:in `block in <top (required)>'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/spork-1.0.0rc4/lib/spork.rb:24:in `prefork'
    from /users/pikachuexe/projects/spacious/spacious-rails/spec/spec_helper.rb:9:in `<top (required)>'
    from /users/pikachuexe/projects/spacious/spacious-rails/spec/rails_helper.rb:1:in `require_relative'
    from /users/pikachuexe/projects/spacious/spacious-rails/spec/rails_helper.rb:1:in `<top (required)>'
    from /users/pikachuexe/projects/spacious/spacious-rails/spec/controllers/users/omniauth_callbacks_controller_spec.rb:1:in `require'
    from /users/pikachuexe/projects/spacious/spacious-rails/spec/controllers/users/omniauth_callbacks_controller_spec.rb:1:in `<top (required)>'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/rspec-core-3.5.0/lib/rspec/core/configuration.rb:1435:in `load'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/rspec-core-3.5.0/lib/rspec/core/configuration.rb:1435:in `block in load_spec_files'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/rspec-core-3.5.0/lib/rspec/core/configuration.rb:1433:in `each'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/rspec-core-3.5.0/lib/rspec/core/configuration.rb:1433:in `load_spec_files'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/rspec-core-3.5.0/lib/rspec/core/runner.rb:100:in `setup'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/rspec-core-3.5.0/lib/rspec/core/runner.rb:86:in `run'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/rspec-core-3.5.0/lib/rspec/core/runner.rb:71:in `run'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/rspec-core-3.5.0/lib/rspec/core/runner.rb:45:in `invoke'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/gems/rspec-core-3.5.0/exe/rspec:4:in `<top (required)>'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/bin/rspec:22:in `load'
    from /users/pikachuexe/.rvm/gems/ruby-2.3.1/bin/rspec:22:in `<top (required)>'
    from -e:1:in `load'
    from -e:1:in `<main>'

fieldnames in error messages not found via i18n scope

first: thanks for the update and everybodys work!

I dont know if this ticket belongs here or in the reform main gem, but since i am using ActiveRecord validations i suppose this is right here.

With reform-rails ( v0.2.0 + reform v2.3.1) using model.errors.full_messages the fieldnames with errors are not found within their 'normal' i18n scope which is either de.attributes.a_name or de.activerecord.attributes.my_model.a_name

it seems as if the method here needs to be overridden to make an i18n lookup instead of a simple capitalize https://github.com/trailblazer/reform/blob/master/lib/reform/errors.rb#L29

Does anybody else has this problem? I would probalby go with a monkey patch as a quick solution but this does not feel right.

Call private method `property` when include Disposable::Twin::Parent

Hi, I'm using Reform with ActiveModel.
When trying to include Disposable::Twin::Parent it reports private method 'property' called error.

Code example:

class Form < Reform::Form
  feature Disposable::Twin::Parent
end

I found the reason could be Reform::Form::ActiveModel defines property as private method, at form_builder_methods.rb#L14-L18.
And Parent will try add a parent property by calling property (parent.rb#L3)

Not sure how to handle it, maybe move the method to public? as it's public in Reform::Form itself.

Not possible to save boolean attributes

With latest released reform 2.2.4 and reform-rails 0.1.7 but also on current master of both: when using it with a boolean attribute (eg postgresql boolean), it is not synced back to model.

rails g model User data_privacy:boolean
# schema.db
create_table "users" [...] do
  t.boolean "data_privacy"
end

# form
class UserForm < Reform::Form
  property :data_privacy
end

# console
form = UserForm.new(User.new)
form.deserialize(data_privacy: true)

form.data_privacy # => true
form.model.data_privacy # => nil

form.sync
form.model.data_privacy # => nil, should be true

form.save # => true
User.last.data_privacy # => nil, should be true

It is not possible to save boolean attributes due to this bug. Or am I doing something wrong?

Nested form objects during validation have the wrong ActiveModel::Name

Say you have two forms, and one is nested in relation to the other, and you have validations on both forms:

class Order < ApplicationRecord
  has_many :items, class_name: OrderItem
end

class OrderItem < ApplicationRecord
end

class OrderForm < Reform::Form
  property :recipient_name
  collection :items, form: OrderItemForm

  validates :recipient_name, presence: true
end

class OrderItemForm < Reform::Form
  validates :product_id, presence: true    # links to some fictitious Product, whatever
end

Also (for the sake of completion) say you have a generic setup like this in your controller:

class OrdersController < ApplicationController
  def create
    order_form = OrderForm.new(Order.new)

    if order_form.validate(params[:order])
      # ...
    else
      # ...
    end
  end
end

Now let's say that you've want to provide custom translations for these validations. You can do this easily for the parent form:

en:
  activemodel:
    errors:
      models:
        order:
          attributes:
            recipient_name:
              blank: "Please provide a name for the recipient."

But if you want to provide translations for the child form...

en:
  activemodel:
    errors:
      models:
        order_item:
          attributes:
            product_id:
              blank: "Please choose a product."

...this won't work. It seems that if you simulate the code in Rails that translates validation messages, you get something interesting:

> order.items[0].class.lookup_ancestors[0].model_name
=> #<ActiveModel::Name:0x007fe1c671d888
 @collection="items",
 @element="item",
 @human="Item",
 @i18n_key=:item,
 @klass=OrderItemForm,
 @name="Item",
 @param_key="item",
 @plural="items",
 @route_key="items",
 @singular="item",
 @singular_route_key="item">

What this means is, I actually need to say this in my locale file:

en:
  activemodel:
    errors:
      models:
        item:
          attributes:
            product_id:
              blank: "Please choose a product."

Now I can fix this by adding model :order_item to OrderItemForm, but 1) I just found out about that and 2) I don't think I should have to do that.

I've been hunting around in Reform and I stumbled upon these lines: https://github.com/trailblazer/reform-rails/blob/master/lib/reform/form/active_model.rb#L34

Could this have anything to do with it? If not, do you know what's happening here?

No way to gradually switch to dry-v

Basically, adding feature Reform::Form::Dry to your form object does nothing, the form continues to use AM::V, unless you switch the whole app with config.reform.validations = :dry.

Case 1, dry-validation is not recognized:

class FooForm < Reform::Form
  feature Reform::Form::Dry

  property :bar, virtual: true

  validation do
    required(:bar).filled
  end
end

> form = FooForm.new(nil)
app/forms/foo_form.rb:2: warning: toplevel constant Dry referenced by Reform::Form::Dry
NoMethodError: undefined method `required' for #<Reform::Form::ActiveModel::Validations::Group:0x007fbf7bcbd738>

Case 2, AM::V still works:

class FooForm < Reform::Form
  feature Reform::Form::Dry

  property :bar, virtual: true
  validates :bar, presence: true
end

> form = FooForm.new(nil)
> form.validate(bar: '')
=> false
> form.errors.class
=> Reform::Form::ActiveModel::Errors

Case 3, switching to dry-v globally, only now it works:

# application.rb
config.reform.validations = :dry

> form = FooForm.new(nil)
> form.validate(bar: '')
=> false
> form.errors.class
=> Reform::Contract::Errors

Versions

* rails (4.2.7.1)
* reform (2.2.1)
* reform-rails (0.1.5)
* dry-validation (0.9.5)

`Reform::Rails` doesn't work with Rails 6.1 well

When using Reform::Rails with Rails 6.1, error messages of form can't acquire.

This is because Reform::Contract::Result expected that error messages are Hash or Array.
https://github.com/trailblazer/reform/blob/bcae40b7405a9ac3ee46507fa2c85acbc997a8c0/lib/reform/result.rb#L37

But in Rails 6.1, error messages are instances of ActiveModel::DeprecationHandlingMessageArray(this due to show deprecate message. Ref: rails/rails#32313). So error messages are all ignored.

This can fix with convert value to Array before check is_a?(Array) . But I think that may not a good approach...

New error objects in rails 6.1

Complete Description of Issue

Hi, I'm trying to upgrade my reform 2.3 app to rails 6.1 and seeing a few issues. I found #86 but did not see any updates so thought I'd ask here if anyone had any ideas.

Rails 6.1 has a new errors object so they should use the new dsl, there are a few convenience methods to maintain compatibility but they are not working well with reform. Specifically form.errors[:some_field] if I want to see the errors on some field (it returns nil).

Steps to reproduce

I reproduced the problem in a new demo rails 6.1 app, here are the relevant parts:

ActiveRecord::Schema.define(version: 2021_01_12_190713) do

  create_table "things", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

end
class ThingForm < Reform::Form
  property :name
  validates :name, presence: true
end
class ThingsController < ApplicationController
...
  # POST /things
  # POST /things.json
  def create
    @form = ThingForm.new(Thing.new)

    respond_to do |format|
      # HERE, if name is NULL, there is no error indexed with the field name, but there is an error present.
      if @form.validate(thing_params)
        @form.save

        format.html { redirect_to @thing, notice: 'Thing was successfully created.' }
        format.json { render :show, status: :created, location: @thing }
      else
        format.html { render :new }
        format.json { render json: @thing.errors, status: :unprocessable_entity }
      end
    end
  end

Expected behavior

If I add a normal active model validation the model will have an error:

class Thing < ApplicationRecord
  validates :name, { presence: true }
end
(byebug) @form.model.errors[:name]
["can't be blank"]

Actual behavior

The form will not have an error for name:

(byebug) thing_params
#<ActionController::Parameters {"name"=>""} permitted: true>
(byebug) @form.validate(thing_params)
false
(byebug) @form.errors[:name]
[]

Some methods will work correctly so it is a little confusing

(byebug) @form.errors.full_messages
["Name can't be blank"]

System configuration

Reform version:
reform 2.3.3
reform-rails 0.2.1

validates: { ... } duplicate error messages

In my form I have such property and its validations declaration:

properties :group, :manager,
           validates: { presence: true, length: 1..12 }
property :warehouse,
         validates: { presence: true, length: 2..8 }

Both when declaring property with property or properties validation errors are duplicate. However, when I separate validations from property declaration:

property :warehouse
validates :warehouse, presence: true, length: 2..8 

Duplication are no longer there. So it seems that validates: { ... } causes error duplication.

I know this issue was reported in trailblazer/trailblazer #109 and was closed as it seemed that updating from reform 2.1 to 2.2 solved it. In my case i use reform-rails 0.1.7 and reform 2.2 and problem is still there or was reintroduced.

Check the form for any errors

I'm working on migrating from reform 2.2.4 to 2.3.3 and from reform-rails 0.1.7 to 0.2.1.

In some places we have a code like this:

form = UserForm.new(user)
form.tap do |form|
  form.validate(params)
  form.errors.add(:base, :custom_error)
end

#####
> form.errors.present?
=> false
> form.errors.any?
=> true

The problem is, that if error was added AFTER form.validate(params) invocation, I cannot check errors existence using form.errors.present?. I guess it is because

# activesupport-6.0.2.1/lib/active_support/core_ext/object/blank.rb
def present?
  !blank?
end

# activesupport-6.0.2.1/lib/active_support/core_ext/object/blank.rb
def blank?
  respond_to?(:empty?) ? !!empty? : !self
end

# reform-rails-0.2.1/lib/reform/form/active_model/validations.rb
def empty?
  @success
end

and @success is not changed if error is added after validation. Therefore, I cannot use form.errors.empty? as well.

Alternatively we can use any? method. But!

It doesn't works as expected in other place where I use nested form:

class ProductForm
  property :data, form: ProductDataForm
end

#####
> form = ProductForm.new(product)
> form.validate(data: { attribute: 'invalid' })
=> false
> form.errors.present?
=> true
> form.errors.any?
=> false

So how should I check if there is any error including nested forms errors or errors added manually after validation?

Thanks in advance.

Bug in uniqueness validation

This is essentially trailblazer/reform#333, i.e. when renaming a unique attribute to a value of another existing entry in the DB that this is not caught using the existing code.

I'd love to write a test case for this but I can't even get the current test suite to pass. rake gives me a lot of error messages (I've checked out v2.2.1 of reform and master is even worse as it produces a segfault). Is there something special I need to do to get this to work?

See errors.txt for the full output of running rake in the top level directory of reform-rails with Ruby 2.3.1.

`#empty?` does not update if error added via `#errors.add(...)`

Using #empty? is a common pattern to check if a form object has any errors or not. For example this method is also used when calling #present?. It should return true if there are no errors and false otherwise.

Currently I'm working on an application that requires adding errors to the form after it was validated. However, adding an error after validating the form does not change the status of #empty?. The following example demonstrates the problem:

class Form < Reform::Form
  property :example
end

form = Form.new(OpenStruct.new)
form.validate({example: 1})
form.errors.empty? # => true
form.errors.add(:example, :invalid)
form.errors.empty? # => true

The last call to #empty? should return false. The same issue also exists when adding a new error to a nested form.

Currently #empty? only checks for the @success instance variable. My first idea was to set this instance variable to false once #add is called on the form's errors. However, this won't work for nested forms because the "parent"-form still does not know about the nested error. Maybe there is another better way of solving this issue?

The current workaround for me is to call validate({}) after adding the errors to ensure the new errors are "recognized" by the form.

calling form.validate throws error stack level too deep

This error occurs when using collection and Disposable::Twin::Parent together
For example

require "reform"
require "reform/rails"
require "disposable"
require "disposable/twin/parent"

class SampleForm < Reform::Form
  feature Disposable::Twin::Parent
  property :name
  validates :name, presence: true

  collection :tags, virtual: true, default: [], populator: :populate_tags! do
      property :name
      validates :name, presence: true

      validate :validate_form_name

      def validate_form_name
        if name == parent.name
          errors.add(:name, "tag name duplicates")
        end
      end
    end

   def populate_tags!(fragment:, **)
      existed_tag = tags.find { |tag| tag.name == fragment[:name] }
      return existed_tag if existed_tag
      tags.append(OpenStruct.new(name: fragment[:name]))
   end
end

form = SampleForm.new
form.save(tags: [ { name: "" } ]) 
form.errors.full_messages # SystemStackError: stack level too deep is thrown right here

Any clues would be truly appreciated, thank you

params as array for collection in a rails form

With a ralis form you can have parameter names with [] as in the next example

<input name="person[phone_number][]" type="text"/>
<input name="person[phone_number][]" type="text"/>
<input name="person[phone_number][]" type="text"/>

In params you receive an array. But in the deserialize! method it always expect a hash as you can see in the code below

    def rename_nested_param_for!(params, dfn)
      name        = dfn[:name]
      nested_name = "#{name}_attributes"
      return unless params.has_key?(nested_name)

      value = params["#{name}_attributes"]
      value = value.values if dfn[:collection]

      params[name] = value
    end

So maybe you can check if the received value is also an hash. values is not a method Array.

Dynamic Field Definition

Hello! There is a desire to implement a dynamic definition of properties based on another model. In the model there is a field jsonb with dynamic attributes store_accessor, implemented as follows:

module Developer
  class Onboarding
    # This model feedback users
    class SurveyResponse < ApplicationRecord
      serialize :feedback, SurveyResponseSerializer

      # This method dynamically defines access attributes based on
      # feedback questions each time when the object is initialized
      def initialize(attributes)
        class << self
          Developer::Onboarding::FeedbackQuestion.all.each do |question|
            store_accessor :feedback, question.key_name.to_sym
            validates question.key_name.to_sym, presence: true
          end
        end
        super(attributes)
      end

      belongs_to :role
      has_one :newcomer, through: :role, source: :user
    end
  end
end

I'm trying to create a model through Reform, but I can not dynamically determine the fields, I tried to do this:

module Developer
  class Onboarding
    class SurveyResponseForm < ::BaseForm
      def initialize *args
        class << self
          Developer::Onboarding::FeedbackQuestion.all.each do |question|
            property question.key_name.to_sym, writeable: true
            validation do
              required(question.key_name.to_sym).filled
            end
          end
        end
        super(*args)
      end
    end
  end
end
[1] pry(main)> Developer::Onboarding::SurveyResponseForm.new(Developer::Onboarding::SurveyResponse.new)
  Developer::Onboarding::FeedbackQuestion Load (0.5ms)  SELECT "developer_onboarding_feedback_questions".* FROM "developer_onboarding_feedback_questions"
  Developer::Onboarding::FeedbackQuestion Load (0.3ms)  SELECT "developer_onboarding_feedback_questions".* FROM "developer_onboarding_feedback_questions"
=> #<Developer::Onboarding::SurveyResponseForm:0x000055bf32c8b490
 @_changes={},
 @errors=#<Reform::Contract::Errors:0x000055bf32c2f208 @errors={}>,
 @fields={},
 @mapper=
  #<#<Class:0x000055bf32c2faf0>:0x000055bf32c2fa28
   @model=
    #<Developer::Onboarding::SurveyResponse:0x000055bf32fadc90
     id: nil,
     feedback: {},
     role_id: nil,
     created_at: nil,
     updated_at: nil>>,
 @model=
  #<Developer::Onboarding::SurveyResponse:0x000055bf32fadc90 id: nil, feedback: {}, role_id: nil, created_at: nil, updated_at: nil>>
[2] pry(main)> exit
➜  give-me-poc git:(73-track-developer-onboarding-progress) ✗

Also, in the development environment, the following definition works:

module Developer
  class Onboarding
    class SurveyResponseForm < ::BaseForm
      Developer::Onboarding::FeedbackQuestion.all.each do |question|
        property question.key_name.to_sym, writeable: true
        validation do
          required(question.key_name.to_sym).filled
        end
      end
    end
  end
end

[1] pry(main)> Developer::Onboarding::SurveyResponseForm.new(Developer::Onboarding::SurveyResponse.new)
Developer::Onboarding::FeedbackQuestion Load (0.3ms) SELECT "developer_onboarding_feedback_questions".* FROM "developer_onboarding_feedback_questions"
Developer::Onboarding::FeedbackQuestion Load (0.2ms) SELECT "developer_onboarding_feedback_questions".* FROM "developer_onboarding_feedback_questions"
=> #<Developer::Onboarding::SurveyResponseForm:0x0000556655b38448
@_changes={},
@errors=#<Reform::Contract::Errors:0x0000556655b1a308 @errors={}>,
@fields={"ewewew"=>nil},
@Mapper=
#<#Class:0x0000556655b1be10:0x0000556655b1b7f8
@model=
#<Developer::Onboarding::SurveyResponse:0x0000556655d048a8
id: nil,
feedback: {},
role_id: nil,
created_at: nil,
updated_at: nil>>,
@model=
#<Developer::Onboarding::SurveyResponse:0x0000556655d048a8 id: nil, feedback: {}, role_id: nil, created_at: nil, updated_at: nil>>
[2] pry(main)>

All fields are required with simple_form and Rails 5

Given a Form object:

class Foo < Reform::Form
  include Reform::Form::ActiveModel::ModelReflections

  property :name, validates: {presence: true}
  property :middle_name, validates: {presence: false}
end

and a corresponding form.haml:

= simple_form_for @form do |form|
  = form.input :name
  = form.input :middle_name
  = form.submit

Both fields are marked as required when I view the form.

versions:

  • reform 2.2.3
  • reform-rails 0.1.7
  • simple_form 3.4.0
  • rails 5.0.1
  • cells-rails 0.0.6
  • trailblazer 2.0.1
  • trailblazer-rails 1.0.2

Using errors.add(:key, 'some text') doesn't translate :key in rails 5

Attribute names are no longer translated. I'm not sure where the problem is, wether it is with reform-rails, i18n and/or rails. Maybe I'm calling the error messages in a wrong way.

Example:

module Documents::Contract
  class Create < Reform::Form

    property :name
    validate :file_has_valid_format?

    private

    def file_has_valid_format?
      errors.add(:name, I18n.t('validation.messages.error.file_type_upload_invalid'))
    end
  end
end

In rails 4 :name was translated to whatever I had defined in activerecord.en.yml:

en:
  activerecord:
    attributes:
      document:
        name: "The X File"
        description: "Descriptionzz"

I'm getting the error message like this:

@form.errors.full_messages.join(". ")

Gemfile.lock:

trailblazer-rails (1.0.2)
      reform-rails (>= 0.1.4, < 0.2.0)
      trailblazer (>= 2.0.0, < 2.1.0)
      trailblazer-loader (>= 0.1.0)

Undefined method persisted? with json field

I'm trying to use postgres JSONB fields using Hash as described in your blogpost.
I'm getting undefined method persisted? for {}:Hash when I try to display json fields using simple_form (and simple_fields_for or fields_for). I have this exact issue even with your example from your blogpost.
Reform version is 2.2.1, reform-rails 0.1.5, disposable 0.3.2 and Rails version is 4.2.7.

reform-rails 0.1.7 incompatible with reform 2.3

Having upgraded reform from 2.2.4 to 2.3.1, I'm getting the following error on app boot:

NameError: uninitialized constant Errors::Merge
gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/active_support.rb:80:in `block in load_missing_constant'
gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/active_support.rb:9:in `without_bootsnap_cache'
gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/active_support.rb:80:in `rescue in load_missing_constant'
gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/active_support.rb:59:in `load_missing_constant'
gems/reform-rails-0.1.7/lib/reform/form/active_model/validations.rb:113:in `<class:Errors>'
gems/reform-rails-0.1.7/lib/reform/form/active_model/validations.rb:112:in `<module:ActiveModel>'
gems/reform-rails-0.1.7/lib/reform/form/active_model/validations.rb:5:in `<main>'

As of reform 2.3, Reform::Form::Errors no longer exists, however reform-rails references it here.

Is it time for 0.2.0 to make it out of rc? If not, a 0.1.8 release that pins reform to 2.2.x would be welcomed. Thanks.

Question: Are nested forms saved in a transaction?

I've used Reform for a few times and every time I start to use nested forms, I stop and ask the question, are nested forms saved in a transaction? My guess would be that they are not, since Reform doesn't know anything about ActiveRecord or any ORM for that matter. I've looked in this gem, and I haven't found anything related to that. I've also looked in Disposable, too, and found this bit of code which saves nested twins. Is this assumption correct?

Delegating from form object causes `ArgumentError` with 0.2.4

I upgraded a Rails app that uses reform-rails from 0.2.3 to 0.2.4 today, and RSpec builds will no longer run due to an apparent delegation-related bug. The app in particular uses Ruby 3.1.2 and Rails 7.0.4.3. Pinning back to 0.2.3, these errors go away.

Example error:

An error occurred while loading ./spec/forms/create_collection_form_spec.rb.
Failure/Error: delegate :persisted?, :to_param, to: :collection

ArgumentError:
  wrong number of arguments (given 3, expected 1)
# ./app/forms/draft_collection_form.rb:97:in `<class:DraftCollectionForm>'
# ./app/forms/draft_collection_form.rb:4:in `<top (required)>'
# /media/mjg/44aee93d-a48a-4ad3-ae90-4b084bb04b69/rvm/gems/ruby-3.1.2@h2/gems/bootsnap-1.16.0/lib/bootsnap/load_path_cache/core_ext/k
ernel_require.rb:32:in `require'
# /media/mjg/44aee93d-a48a-4ad3-ae90-4b084bb04b69/rvm/gems/ruby-3.1.2@h2/gems/bootsnap-1.16.0/lib/bootsnap/load_path_cache/core_ext/k
ernel_require.rb:32:in `require'
# /media/mjg/44aee93d-a48a-4ad3-ae90-4b084bb04b69/rvm/gems/ruby-3.1.2@h2/gems/zeitwerk-2.6.8/lib/zeitwerk/kernel.rb:30:in `require'
# ./app/forms/create_collection_form.rb:4:in `<top (required)>'
# /media/mjg/44aee93d-a48a-4ad3-ae90-4b084bb04b69/rvm/gems/ruby-3.1.2@h2/gems/bootsnap-1.16.0/lib/bootsnap/load_path_cache/core_ext/k
ernel_require.rb:32:in `require'
# /media/mjg/44aee93d-a48a-4ad3-ae90-4b084bb04b69/rvm/gems/ruby-3.1.2@h2/gems/bootsnap-1.16.0/lib/bootsnap/load_path_cache/core_ext/k
ernel_require.rb:32:in `require'
# /media/mjg/44aee93d-a48a-4ad3-ae90-4b084bb04b69/rvm/gems/ruby-3.1.2@h2/gems/zeitwerk-2.6.8/lib/zeitwerk/kernel.rb:30:in `require'
# ./spec/forms/create_collection_form_spec.rb:5:in `<top (required)>'

Here's the form object in question: https://github.com/sul-dlss/happy-heron/blob/main/app/forms/draft_collection_form.rb#L97

JSON property with nested data

If model has JSON field then it is possible to add nested properties for JSON to contract?

Example:

models/station.rb

class Station < ActiveRecord::Base
  serialize :settings, JSON
end

concepts/station/update.rb

class Station::Update < Trailblazer::Operation
  contract do
    property :settings do
      property :briefing
      collection :routes do
        property :name
        ...
      end
      ...
    end
  end
end

Then gem raises error message undefined method 'briefing' for #<Hash:0x007fd47c200aa0>

What are the ways to solve this problem?

Unable to use `id` in ActiveModel validations since reform-rails 0.2.4

Hi!

We are struggling to update reform-rails from 0.2.3 to 0.2.5 (we also had this same struggle with reform-rails version 0.2.4).

The problem seems to stem from us having this kind of validation:

property :id, virtual: true
property :_destroy, virtual: true
property :photo

validates :_destroy, presence: true, if: -> { id }
validates :id, presence: true, if: -> { _destroy }

And we have this kind of validation call:

form.validate(photo: a_new_image)

With reform-rails 0.2.4 and 0.2.5, we get a validation error saying that _destroy can't be blank while with 0.2.3 we don't get that.

The change is that in reform-rails 0.2.3, the value of id in the if: -> { id } lambda was read from the parameters passed to form.validate while in 0.2.5, the value of id is read from the form.model. I think the problem is somehow caused by 294de5f and #100 but I don't really understand why.

Is it possible that with the old Uber::Delegates, the id delegation was somehow different than how it is now with Rails provided delegate method?


Here's the full source code for our form

# frozen_string_literal: true
module Profiles::Common
  class ProfileEditImagesForm < Reform::Form
    model :profile

    ImagePopulator =
      lambda do |fragment:, collection:, **|
        # `fragment` represents the incoming params piece regarding one image.
        # `collection` represents the methods usable to manage the `images` collection.
        # http://trailblazer.to/gems/reform/populator.html

        if (fragment[:_destroy] && !fragment[:id]) ||
             (fragment[:id] && !fragment[:_destroy])
          # Validations will prevent this kind of populator from going through. We just
          # need this populator to return some valid value so that Reform won't choke yet,
          # but one that allows Reform to provide validation errors anyway. Whew.
          return collection.append(::Profiles::Common::ProfileImage.new)
        end

        if fragment[:_destroy]
          item =
            collection.find { |itm| itm.model.id.to_s == fragment[:id].to_s }
          collection.delete(item) if item
          skip!
        else
          collection.append(::Profiles::Common::ProfileImage.new)
        end
      end

    # `save: false` stops existing images being touched when collection is changed
    collection :images, populator: ImagePopulator, save: false do
      property :id, virtual: true
      property :_destroy, virtual: true
      property :photo

      validates :id, presence: true, if: -> { _destroy }
      validates :_destroy, presence: true, if: -> { id }
    end
  end
end

And here's how we have a unit test for it:

it 'does not modify existing images' do
  existing_image = create(:image, :with_real_file)
  profile = create(Profiles::SupplierProfile.factory_name, images: [existing_image])
  form = Profiles::Common::ProfileEditImagesForm.new(profile)

  new_image = fixture_file_upload('lol.png', 'image/png')
  result = form.validate(images: [{ photo: new_image }])
  puts "We have errors but shouldn't: #{form.errors.messages}"
  assert(result)
  expect { form.save }.not_to(change { existing_image.reload.updated_at })
end

Back in reform-rails 0.2.3, this code didn't have errors but now with 0.2.5, we get an error:

We have errors but shouldn't: {:"images._destroy"=>["can't be blank"]}

`uninitialized constant Reform::Form::ActiveModel::ActiveModel` in Rails 5 with dry

Good day! I use Rails 5, reform-rails 0.1.5, dry-validation 0.9.5.

When setting validation engine to dry-validation in initializer:

Rails.application.config.reform.validations = :dry

loading fails:

/Users/tap/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-rails-0.1.5/lib/reform/form/active_model.rb:7:in `block in included': uninitialized constant Reform::Form::ActiveModel::ActiveModel (NameError)
    from /Users/tap/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-rails-0.1.5/lib/reform/form/active_model.rb:5:in `class_eval'
    from /Users/tap/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-rails-0.1.5/lib/reform/form/active_model.rb:5:in `included'
    from /Users/tap/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-rails-0.1.5/lib/reform/rails/railtie.rb:49:in `include'
    from /Users/tap/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-rails-0.1.5/lib/reform/rails/railtie.rb:49:in `block in dry!'
    from /Users/tap/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-rails-0.1.5/lib/reform/rails/railtie.rb:48:in `class_eval'
    from /Users/tap/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-rails-0.1.5/lib/reform/rails/railtie.rb:48:in `dry!'
    from /Users/tap/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-rails-0.1.5/lib/reform/rails/railtie.rb:12:in `block in <class:Railtie>'
...

I have figured it out that it's also necessary to require 'reform/form/active_model/validations' (in addition to 'reform/form/active_model'):

require 'reform/form/active_model/validations'

Rails.application.config.reform.validations = :dry

(or somewhere after this line: https://github.com/trailblazer/reform-rails/blob/master/lib/reform/rails/railtie.rb#L46)

this way it works.
IDK if it matters but I have also disabled AR in my application.rb (using rom-rb - yeah, a strange combo :) ):

# require 'active_record/railtie'

`validate` false positive when property name is `Object` method

See trailblazer/reform#314 for conversation.

Original description:

Might not be only Object methods, but is definitely at least those:

require "reform/form/active_model/validations"
class BadForm < Reform::Form
  include Reform::Form::ActiveModel::Validations

  property :trust        # or anything from `Object.public_methods`
  validates :trust, presence: true
end

BadForm.new(Struct.new(:trust).new).validate({})
# => true

ActiveModel validaitons not working

Hi,

In my project, when using:

extend ActiveModel::ModelValidations

 copy_validations_from Model

The validations from my models are not backported to the Form.

After taking a look at the code it seems like the issues is coming from Mapping.from_representable_attrs(definitions) in the copy_validations_from method, definitions is always returning {} which prevent the mapping of the attributes in from_representable_attrs and prevent the method inverse_image to return any value.

Did I miss an element to set to make the validations work?

Nested MultiParameter Attributes not converted

Dates that are nested don't seem to be converted from multi params to a date. For example

  include Reform::Form::MultiParameterAttributes
  
  property :something_nested do
    property :some_date, multi_params: true

It looks like it skips over the logic to convert the params to date because the nested params come in as type ActionController::Parameters and not as a Hash

I was able to fix this in a forked branch by doing

      params.each do |attribute, value|
        if value.respond_to?(:to_hash) #instead of value.is_a?(Hash)
          params[attribute] = call(value) # TODO: #validate should only handle local form params.
        elsif matches = attribute.match(/^(\w+)\(.i\)$/)
          date_attribute = matches[1]
          date_attributes[date_attribute] = params_to_date(
            params.delete("#{date_attribute}(1i)"),
            params.delete("#{date_attribute}(2i)"),
            params.delete("#{date_attribute}(3i)"),
            params.delete("#{date_attribute}(4i)"),
            params.delete("#{date_attribute}(5i)")
          )
        end
      end

Any thoughts on a better way to do this or perhaps I am missing some options?

Update reform-rails to work with new reform API

New Reform APIs

We need to update reform-rails to work with the new super simple Errors API.

Support for Custom Validations

Custom validations work by calling errors.add(:key, value) if a condition is met. This isn't supported in Reform core and will need to be added.

Form Builders

Our Errors API needs to provide methods expected by rails form builders [see] (trailblazer/reform#423)

Dry-Validation: Strange i18n behavior

Complete Description of Issue

I am using my own base class MyApp::Contract < Reform::Form for all my Reform-Objects and have configured Dry::Validation::Schema to use i18n messages. My locale is de. If I submit my form with invalid data, I don't get the correct local error message but the default en one. If I just edit and save my base class with just adding or deleting a blank line while puma is running and resubmit the form, I get the correct de locale. I am not sure if this bug is in Reform, Rails, Puma or wherever.

Update: Editing and saving the derived class has the same result
Update: Feature Testing with cucumber leads to wrong localization, too. So Puma seems not to be the problem.
Update: Even when changing the derived class as follows, I get the same behavior:

module User::Contract
  class Create < MyApp::Contract
    property :forename
  
    validation do
      configure { config.messages = :i18n }
      required(:forename).filled
    end
  end
end

Update: When overwriting

en:
  errors:
    filled?: "bitte ausfüllen"

I get this german text. So there seems to be use of i18n, but the wrong locale is used.

Steps to reproduce

  1. initializer for dry-validation and locale:
module MyApp
  class Application < Rails::Application
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
    config.i18n.default_locale = :de
  end
end

Dry::Validation::Schema.configure do |config|
  config.messages = :i18n
end

Dry::Validation::Schema::Form.configure do |config|
  config.messages = :i18n
end
  1. locale:
de:
  errors:
    filled?: "bitte ausfüllen"
  1. Base class:
require 'reform'
require 'reform/form/dry'

module MyApp
  class Contract < Reform::Form
    include Dry
  end
end
  1. Concrete class:
module User::Contract
  class Create < MyApp::Contract
    property :forename

     validation do
      required(:forename).filled
    end
  end
end

Expected behavior

Error message should be bitte ausfüllen when form is submitted without filling forename

Actual behavior

Error message is must be filled. After I edit and save the file where the base class is in (step 3. from above) while puma is running, bitte ausfüllen is shown after submit as expected.

System configuration

Reform version: 2.2.4
Reform Rails version: 0.1.7
Trailblazer Rails version: 0.1.7
Rails version: 5.1.4

valid false value throws validation error (blank) in RSpec

Note: If you have a question about Reform, would like help using
Reform, want to request a feature, or do anything else other than
submit a bug report, please use the Trailblazer gitter channel.

^ The link to the gitter channel 401'd for me. Moving now that Gitlab purchased? Is there a freenode IRC channel I could hit up instead?

Complete Description of Issue

I have a form with a checkbox. In the form object, this checkbox property is required to be present. (validates :can_sms, presence: true) In the underlying model, this column has a nullable Boolean corresponding.

In RSpec, I'm testing my model's validations without reference to my view code, i.e.

  let(:valid_user) { build(:user, can_sms: true) }
  it 'is valid when user and user contact attrs provided' do
    valid_user.user_contact = build(:user_contact)

    form = Ola::StepOne.new(valid_user)
    form.valid?

    expect(form.errors).to be_empty
  end

I have to manually set can_sms: true above because can_sms: false throws a blankness error. (Usually the User attributes from the factory_girl factory would set can_sms: false.)

When the form is submitted through my view code (a simple .erb template) no such blankness error occurs even when the checkbox is unchecked (so attribute value at that point is false). I figure this is my best lead, and when I have some time I intend to try to chase it along so I can give you better steps to reproduce.

Expected behavior

false should be considered a valid value for the form and not throw a validation error.

Actual behavior

false seemingly (my guess?) is coerced to nil, and then throws a validation error for blankness.

System configuration

Reform version:

    reform (2.2.4)
    reform-rails (0.1.7)
      reform (>= 2.2.0)

Unknown validator: 'UniquenessValidator'

I cloned your project, then I installed gems:

bundle install

And after running command:

bundle exec rake test

I got error:

config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly:

  * development - set it to false
  * test - set it to false (unless you use a tool that preloads your test environment)
  * production - set it to true

/home/quolpr/.rvm/gems/ruby-2.3.1/gems/activemodel-4.2.6/lib/active_model/validations/validates.rb:120:in `rescue in block in validates': Unknown validator: 'UniquenessValidator' (ArgumentError)
    from /home/quolpr/.rvm/gems/ruby-2.3.1/gems/activemodel-4.2.6/lib/active_model/validations/validates.rb:117:in `block in validates'
    from /home/quolpr/.rvm/gems/ruby-2.3.1/gems/activemodel-4.2.6/lib/active_model/validations/validates.rb:113:in `each'
    from /home/quolpr/.rvm/gems/ruby-2.3.1/gems/activemodel-4.2.6/lib/active_model/validations/validates.rb:113:in `validates'
    from /home/quolpr/projects/github/reform-rails/test/uniqueness_validator_test.rb:5:in `<class:FormWithLeakyUniquenessValidation>'
    from /home/quolpr/projects/github/reform-rails/test/uniqueness_validator_test.rb:3:in `<top (required)>'
    from /home/quolpr/.rvm/gems/ruby-2.3.1/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
    from /home/quolpr/.rvm/gems/ruby-2.3.1/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `block in require'
    from /home/quolpr/.rvm/gems/ruby-2.3.1/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:240:in `load_dependency'
    from /home/quolpr/.rvm/gems/ruby-2.3.1/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
    from /home/quolpr/.rvm/gems/ruby-2.3.1/gems/rake-10.5.0/lib/rake/rake_test_loader.rb:15:in `block in <main>'
    from /home/quolpr/.rvm/gems/ruby-2.3.1/gems/rake-10.5.0/lib/rake/rake_test_loader.rb:4:in `select'
    from /home/quolpr/.rvm/gems/ruby-2.3.1/gems/rake-10.5.0/lib/rake/rake_test_loader.rb:4:in `<main>'
rake aborted!
Command failed with status (1): [ruby -I"lib:test:lib" -I"/home/quolpr/.rvm/gems/ruby-2.3.1/gems/rake-10.5.0/lib" "/home/quolpr/.rvm/gems/ruby-2.3.1/gems/rake-10.5.0/lib/rake/rake_test_loader.rb" "test/reform/rails_test.rb" "test/uniqueness_validator_test.rb" ]
/home/quolpr/.rvm/gems/ruby-2.3.1/bin/ruby_executable_hooks:15:in `eval'
/home/quolpr/.rvm/gems/ruby-2.3.1/bin/ruby_executable_hooks:15:in `<main>'
Tasks: TOP => test
(See full trace by running task with --trace)

Integration simple_for and reform 2.3.0

Repository to reproduce error: https://github.com/onemanstartup/reform_rails_simple_for_bug

Main thing is error happens with reform 2.3.0.rc1

= simple_form_for(@form) do |f|
  .fieldset
    = f.input :name
    = f.button :submit
undefined method `+' for nil:NilClass

simple_form (3.4.0) lib/simple_form/components/errors.rb:33:in `errors'
simple_form (3.4.0) lib/simple_form/components/errors.rb:13:in `has_errors?'

Reform::Contract::Result::Errors when asked attribute returns nil, but ActiveRecord object Product.new.errors[:name] returns empty array. Simple form can't handle nils and throws up.
Even when I remove validation block in form there is still error.

validates_uniqueness_of doesn't work in validation groups

Basically, it's only implemented as a method on the form itself, but not on the validation group.
So the following works:

class MyForm < Reform::Form
  validates_uniqueness_of :foo
end

and this doesn't:

class MyForm < Reform::Form
  validation :bar do
    validates_uniqueness_of :foo
  end
end

To make it clear, this is not a feature request, it's a shortcoming in the existing API.

validate returns true although nested property has AMV error

I created a test to show the unexpected behavior in https://github.com/sliminas/reform-rails/commit/9bf826000cfe3e800fd6bd3c8ae90c5d63562019

I already tracked down the issue to this line in the code https://github.com/trailblazer/reform-rails/blob/master/lib/reform/form/active_model/validations.rb#L62

def validate!(params, pointers=[])
  @amv_errors = ActiveModel::Errors.new(self)

  super.tap do
    # @fran: super ugly hack thanks to the shit architecture of AMV. let's drop it in 3.0 and move on!
    all_errors = @result.instance_variable_get(:@results)

    @result = Reform::Contract::Result.new(all_errors)

If you look at @Result at the first time we arrive there during the test it looks like this and all_errors will be the AMV error message:

#<Reform::Contract::Result:0x0055faa3f42a10 @failure={:length=>["must be greater than 55"]}, @results=[{:length=>["must be greater than 55"]}]>

But we get there a second time later one, where @result looks like this:

#<Reform::Contract::Result:0x0055b0a4c3ad98
 @failure=
  #<Reform::Contract::Result:0x0055b0a4c3b400 @failure={:length=>["must be greater than 55"]}, @results=[{:length=>["must be greater than 55"]}]>,
 @results=[]>

But there the instance variable @results is empty and the success? method will return true, although there are errors present in the contract.

I used ruby 2.3.1 for the test.

`as_json`: stack level too deep

On current master of reform-rails and reform:

With the following form:

class SomeForm < ReformForm
  property :first_name
  validates :first_name, presence: true
end

... this happens:

form = SomeForm.new(User.new)
form.as_json # => prints out some stuff correctly

form.validate(first_name: '') # => false

# after validation, this does not work anymore:
form.as_json # => SystemStackError: stack level too deep

Form inheritance raises for `validates_with` when not wrapped in a validation group

Steps to reproduce

Having the following form declaration:

class FooForm < Reform::Form
  property :foo
  validates_with DoesntMatterValidator, attributes: [:foo]
end

class DoesntMatterValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value); end # whatever here
end

Call Class.new(FooForm).

Expected behavior

A child class of FooForm created.

Actual behavior

Exception ArgumentError: :attributes cannot be blank raised.

System configuration

Reform-rails version: 0.1.3 (also, applies to master as of now)
Reform version: 2.2.1

Full Backtrace of Exception

ArgumentError: :attributes cannot be blank
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activemodel-4.2.6/lib/active_model/validator.rb:139:in `initialize'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activemodel-4.2.6/lib/active_model/validations/with.rb:93:in `new'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activemodel-4.2.6/lib/active_model/validations/with.rb:93:in `block in validates_with'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activemodel-4.2.6/lib/active_model/validations/with.rb:92:in `each'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activemodel-4.2.6/lib/active_model/validations/with.rb:92:in `validates_with'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-2.2.1/lib/reform/validation.rb:27:in `block in validates_with'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-2.2.1/lib/reform/validation.rb:15:in `instance_exec'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/reform-2.2.1/lib/reform/validation.rb:15:in `validation'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/declarative-0.0.8/lib/declarative/heritage.rb:18:in `call!'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/declarative-0.0.8/lib/declarative/heritage.rb:11:in `block in call'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/declarative-0.0.8/lib/declarative/heritage.rb:11:in `each'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/declarative-0.0.8/lib/declarative/heritage.rb:11:in `call'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/disposable-0.3.2/lib/disposable/twin.rb:19:in `inherited'
from (irb):4:in `initialize'
from (irb):4:in `new'
from (irb):4
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/railties-4.2.6/lib/rails/commands/console.rb:110:in `start'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/railties-4.2.6/lib/rails/commands/console.rb:9:in `start'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/railties-4.2.6/lib/rails/commands/commands_tasks.rb:68:in `console'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/railties-4.2.6/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
from /Users/apotonick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/railties-4.2.6/lib/rails/commands.rb:17:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'

Helpful info

The problem is activemodel mutates arguments that you pass to validates_with, so while usually this doesn't matter, because they aren't used anymore, reform accidentally reuses them.
First, validations are initialized for the class itself, which works fine. But it fails on the second pass, which happens if you inherit from a form class(which TRB does).

Here's the culprit:

validation(:default, inherit: true) { validates_with *args, &block }

The block captures the args. Note, this doesn't happen if you declare the validation group explicitly, because then args are not captured.

A quick solution would be to use args.dup, what do you think, @apotonick ?

respond_to? method only takes one parameter

/work/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/super_diff-0.8.0/lib/super_diff/object_inspection/inspection_tree_builders/default_object.rb:46: warning: Reform::Form::ActiveModel::Validations::Result::ResultErrors#respond_to?(:to_hash) uses the deprecated method signature, which takes one parameter
/work/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/reform-rails-0.2.2/lib/reform/form/active_model/validations.rb:165: warning: respond_to? is defined here

It should take an optional second argument include_all: https://ruby-doc.org/core-3.0.2/Object.html#method-i-respond_to-3F

Delegation-related errors with 0.2.4 and 0.2.5

The 0.2.5 release fixed the ArgumentError I wrote up in #99, but I'm now seeing odd behavior in an app that uses reform-rails (and I see the error when Reform::Form::ActiveModel.included does delegation via delegate or def_delegators but not when it uses Uber::Delegates).

More about my app:

I tossed a debugger in the form to sniff at the value of tags which comes through the form's populator, and here's a difference that seems relevant:

# With 0.2.4 and 0.2.5
(byebug) tags.first.id
"Some : Thing" # this is surprising, and the cause of the failing test
(byebug) tags.first.method(:id)
#<Method: #<Class:0x00007f88bef324f8>#id(...) /media/mjg/44aee93d-a48a-4ad3-ae90-4b084bb04b69/rvm/gems/ruby-3.2.2@argo/gems/reform-rails-0.2.5/lib/reform/form/active_model.rb:9>

# With 0.2.3
(byebug) tags.first.id
""
(byebug) tags.first.method(:id)
#<Method: #<Class:0x00007f912389ae70>(#<Module:0x00007f9123899b10>)#id() /media/mjg/44aee93d-a48a-4ad3-ae90-4b084bb04b69/rvm/gems/ruby-3.2.2@argo/gems/disposable-0.6.3/lib/disposable/twin.rb:72>

So, naïvely, this seems related to how id delegation is being handled?

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.