Giter VIP home page Giter VIP logo

reform'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's People

Contributors

apotonick avatar arturictus avatar azdaroth avatar bdewater avatar blelump avatar cameron-martin avatar emaglio avatar fernandes avatar fran-worley avatar francirp avatar freemanoid avatar gdott9 avatar gogogarrett avatar jdwolk avatar levinalex avatar lime avatar lukeasrodgers avatar mattheworiordan avatar mjonuschat avatar myabc avatar pabloh avatar parndt avatar richardboehme avatar seuros avatar simoniong avatar slashek avatar tevanoff avatar timoschilling avatar twonegatives 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  avatar  avatar  avatar

Watchers

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

reform's Issues

Rails has_one and *_attributes params

Hi,

I'm trying to use reform to provide a form for users to modify their profile.
The profile form uses the user itself (a Student here) and can also modify the account email attribute.

I'm using the code that follows but the generated form uses student[account_attributes][email] instead of student[account][email].
I know it's expected from Rails but my form object is not so fond of it...

I get an error saying

undefined method `has_key?' for nil:NilClass
# in Representable::Hash::PropertyBinding#read

because at some point it tries to access params['student']['account'] and it returns nil.

Is it supposed to work out of the box and I'm missing something or is it something I'm supposed to work around ?

Thanks

# app/models/user.rb
class Student < ActiveRecord::Base
  has_one :account,
    as: :user,
    inverse_of: :user
end

# app/models/account.rb
class Account < ActiveRecord::Base
    belongs_to :user,
      inverse_of: :account,
      polymorphic: true,
      touch: true
end

# app/forms/student_profile_form.rb
class StudentProfileForm < Reform::Form
  include Reform::Form::ActiveRecord

  property :account do
    property :email
  end

  properties :firstname, :gender, :lastname, :phone_number

  model :profile
end

# app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
  def edit
    @profile_form = StudentProfileForm.new(current_student)
  end

  def update
    @profile_form = StudentProfileForm.new(current_student)
    if @profile_form.validate(params) && @profile_form.save
      redirect_to profile_path
    else
      render action: 'edit'
    end
  end
end
/ app/views/profiles/edit.html.slim
= simple_form_for @profile_form do |f|
  = f.input :firstname
  / ...
  = f.fields_for :account do |a|
    = a.input :email

Inherit model validation?

Hi,

Does reform inherit the validation from the model for the fields associated with it, or do you have to re-add the validation you want?

Collection inside of nested property throws an error

Hi folks!

By the time of writing this issue, it is already classified as a bug and is getting fixed.
I just wanna to let you know about it.

The model situation is described below.
Let's have a form class like this:

class DocumentForm < Reform::Form
...
  property :a do
    collection :b do
    ...
    end
  end
...
end

Then, when creating the form object with provided models' object...

DocumentForm.new(@document)

...is everything ok, but only if the provided models' object has defined both the :a and :b.

But (this is the issue), if :a is nil and thus the :b is nil too, the reform is trying to assign the :b anyway, but can't because the outer :a is nil.

Have a nice day! :-)

Feature request: write form values back to the model but don't call model#save

Reform already does this with any property that has validations. I would love for it to also do this with any other [non-virtual] properties I declare.

My reason for this is simple: sometimes I have to do some additional custom processing before I #save the model – so I can't just use Form#save w/o a block. But because I already do my validations inside of Reform, I don't use strong parameters, and that means inside of the Form#save block I either have to initialize a new model instance and pass it all of the params (not great if there are virtual/empty params being used) OR re-use reform.model and set everything by hand.

So this is what I envision:

# person_form.rb
class PersonForm < Reform::Form
  property :name
  property :description

  validates :name, presence: true
end

User submits the form and all validations pass. Meanwhile, in the controller:

# person_controller.rb
class PersonController
  def process_form
    #assume @form was initialized somewhere else with either a Person.new instance (e.g. on New) OR an existing Person from the DB (e.g. on Edit)
    # This is a standard use case w/ things like decent_exposure
    if @form.validate(params[:person])
        @form.save do |form, data|
            Rails.logger.info @form.model.name           # => "Arthur" (Reform already does this today, because there was a validator on :name)
            Rails.logger.info @form.model.description     # => "Cool guy" (Feature request)

            @form.model.name.upcase!        # My custom manipulation

            @form.model.save!                      # now I can save things happily.
         end
    end
  end
end

undefined method `to_key` for a <put a Reform::Form class here>

I recognize that this can be easily avoided by adding a to_key method as written in your example here: https://github.com/gogogarrett/reform_example/blob/master/app/forms/forms/artist_form.rb

... but why should it be?

Here's a rough sample of my form (w/ to_key):

class ReservationForm < Reform::Form
  include DSL
  properties [:first_name, :last_name], on: :visitor
  properties [:credit_card_number, :credit_card_cvv], on: :billing

  def to_key
    [1]
  end
end

Using empty fields on composition objects

Attempting to use an empty field such as property :password_confirmation, :empty => true causes an error when including Composition.

To reproduce

clone https://github.com/grossadamm/ReformEmptyWithComposition

or

setup the project

rails new emptytest/

add reform to Gemfile

bundle
rails g model song title:string
rails g model label city:string
rake db:migrate
mkdir app/forms
mkdir test/forms

add these files

app/forms/empty_with_composition.rb

  class EmptyWithComposition < Reform::Form
    include Composition
    property :title, on: :song
    property :city,  on: :label
    property :empty_thing, :empty => true
  end

test/forms/empty_with_composition_test.rb

  require 'test_helper'

  class EmptyWithCompositionTest < ActiveSupport::TestCase

    test "should be ok with empty thing" do 
        form = EmptyWithComposition.new(song: Song.new, label: Label.new)
        validation = form.validate(
            "title" => "my title",
            "city" => "my cool city",
            "empty_thing" => "empty"
      )
        assert validation
    end
  end

run the test

rake test test/forms/empty_with_composition_test.rb

see error

   Run options: --seed 708

   # Running tests:

   E

   Finished tests in 0.035624s, 28.0708 tests/s, 0.0000 assertions/s.

     1) Error:
   EmptyWithCompositionTest#test_should_be_ok_with_empty_thing:
   SyntaxError: /home/agross/.rvm/rubies/ruby-2.0.0-p353/lib/ruby/2.0.0/forwardable.rb:169: syntax error, unexpected *
         def (*args, &block)
               ^
   /home/agross/.rvm/rubies/ruby-2.0.0-p353/lib/ruby/2.0.0/forwardable.rb:169: syntax error, unexpected &
         def (*args, &block)
                      ^
       app/forms/empty_with_composition.rb:5:in `<class:EmptyWithComposition>'
       app/forms/empty_with_composition.rb:1:in `<top (required)>'
       test/forms/empty_with_composition_test.rb:6:in `block in <class:EmptyWithCompositionTest>'

   1 tests, 0 assertions, 0 failures, 1 errors, 0 skips

Rename `on:` to `key:`

I find on to be a very unclear word to describe what the property method achieves.

Take the following example:

property :email,        on: :user
properties [:gender, :age],   on: :profile

model :user 

On first glance, it looks as if the property email is defined on the user model. It's not immediately obvious that the on specifies the key in the params hash.

It gets even more hazy when you start reading:

model :singer, :on => :user

I propose the alternative. You could support on for backward compatibility

property :email,        key: :user
properties [:gender, :age],   key: :profile

model :user 

and

model :singer, :key => :user

If you like it, I'll start making the necessary changes.

Multiple level of nesting

I'm trying to handle a deep nested structure in a form - I realise that maybe I shouldn't be:-). However, this is part of wizard and it simplifies the code to allow this deep nesting.
Although the price bands are being passed in the params they are not getting populated in the form (the form has an empty array).

Am I making some simple mistake or is the problem with Reform?

thanks

jon

class Product < ActiveRecord::Base
  has_one :pricing_model, dependent: :destroy
end

class PricingModel < ActiveRecord::Base
  belongs_to :product
  has_many :price_bands, dependent: :destroy
end

class PriceBand < ActiveRecord::Base
  belongs_to :pricing_model
end

class PricingForm < Reform::Form
  include Reform::Form::ActiveModel
  include Reform::Form::ActiveModel::FormBuilderMethods

  model :product

  property :pricing_model do
    property :pricing_strategy_id
    property :base_price

    collection :price_bands do
      property :phase
      property :start_value
     end
  end
end

    Parameters: {"utf8"=>"✓", "authenticity_token"=>"mvRdAn/0Kjka/TJ7kYgINzUxVzwQUN6DBrz0RxpANtE=", "product"=>{"pricing_model_attributes"=>{"pricing_strategy_id"=>"1", "base_price"=>"24", "variable_price"=>"43", "variable_name"=>"number_in_roll", "quoted_product_details"=>"", "price_bands_attributes"=>{"0"=>{"phase"=>"1", "start_value"=>"23", "end_value"=>"43", "_destroy"=>"false"}}, "id"=>"133"}}, "commit"=>"Next", "product_id"=>"147", "id"=>"pricing"}


#<ProductWizard::PricingForm:0xb576c68
 @errors=
  #<Reform::Form::Errors:0xb2f4990
   @base=#<ProductWizard::PricingForm:0xb576c68 ...>,
   @messages={}>,
 @fields=
  #<Reform::Fields pricing_model=#<#<Class:0xb2f7d70>:0xbca5cdc @model=#<PricingModel id: 133, product_id: 147, pricing_strategy_id: 1, base_price: 24, variable_name: "number_in_roll", variable_price: 43, manual_price: nil, quoted_product_details: "", apply_discount: nil, discount_start_date: nil, discount_end_date: nil, discount_type: nil, discount_minimum_order_quantity: nil, discount_calculation: nil, discount_amount: nil, invoicing_basis: nil, invoicing_rule: nil, created_at: "2014-02-15 16:48:44", updated_at: "2014-02-15 17:11:51">, @fields=#<Reform::Fields pricing_strategy_id="1", base_price="24", quoted_product_details="", variable_name="number_in_roll", variable_price="43", price_bands=[]>, @errors=#<Reform::Form::Errors:0xb2f4544 @base=#<#<Class:0xb2f7d70>:0xbca5cdc ...>, @messages={}>, @validation_context=nil>>,
 @model=
  #<Product id: 147, type: "Product", parent_id: 108, service_parent_id: nil, product_parent_id: nil, classification_id: nil, name: "Product #2", customer_type: "ABC", service_year_id: nil, subscription_period: "All", sku: 10147, short_description: "", long_description: nil, core: nil, built: false, created_at: "2014-02-15 16:48:44", updated_at: "2014-02-15 16:48:54">,
 @validation_context=nil>


How to get the validate result without validate again

I have a multi row form for update data

def set_session_forms(sessions)
      session_forms = []
      sessions.each do |session|
        session_forms.push({model: session, form: ActivitySessionForm.new(session)})
      end
      session_forms
end
<%= simple_form_for :sessions %>
  <% @session_forms.each do |session| %>
    <%= simple_fields_for "sessions[#{session[:form].id}]", session[:form] do |f| %>
     ....
    <% end %>
  <% end %>
<% end %>

i put reform to a array and run validate in loop, if any one validate is false, then render the view to display the error rows. i need a validate result cache like below

<%= simple_fields_for "sessions[#{session[:form].id}]", session[:form] do |f| %>
  <tr>
    <% if session[:form].is_valid? # from cache %> 
      ... error things
    <% end %>
  </tr>
<% end %>

other one thing, i use sessions[:form], because when i valid the params pass, i need to get the model to save, so i have to store the model to other place, any idea can use forms to get model in this version 2.0.0

i think it must have requirement to get model from reform, or other solution to access the activerecord dirty attrs

@session_form = SessionForm.new Session.new
@session_form.model.new_record?

I am a rails beginner, i don't know what is the better solution, if i am wrong please let me know. thanks.

Avoiding duplication of validates method

I have a model with validation and form with validation. Why I duplicate some validation methods from model to form? It isn't dry!

Is there any ways to avoid this bad behaviour?

Issue with date field being blank

I have a date field in a a form and I check that the date is present.
When I submit the form I get a validation error saying that the date can't be blank.
Is it due to the way date fields are sent ?

app/controllers/legal_details_controller.rb

class LegalDetailsController < ApplicationController
  expose(:legal_details_form) do
    LegalDetailsForm.new(current_user)
  end

  def update
    if legal_details_form.validate(params[:legal_details])
      # ...
    end
  end
end

app/forms/legal_details_form.rb

class LegalDetailsForm < Reform::Form
  include Reform::Form::ActiveRecord

  properties %i[birthdate]

  model :legal_details

  validates :birthdate,
    presence: true
end

app/views/legal_details/edit.html.slim

= simple_form_for legal_details_form, url: legal_details_path do |f|
  = f.input :birthdate, as: :date
  = f.button :button

params

"legal_details" => {
  "birthdate(3i)" => "1",
  "birthdate(2i)" => "1",
  "birthdate(1i)" => "2013"
}

error: undefined method `persisted?'

on rails 4 with Composition included I have to specify

  model :birnen, on: :birnen

otherwise I get an error: undefined method persisted?'` Not sure if this is an bug or just need to be better documentation, maybe a hint at the composition section.

Using nested objects doesn't populate decedent items/models. (Plus saving strategies)

If I do something like

class TagForm < Reform::Form
  property :category
  property :primary_label do
    property :name
  end
  collection :aliased_labels do
    property :name
  end
end

test = TagForm.new Tag.new.tap &:build_primary_label
stuff = { "category" => "classification", "primary_label_attributes" => { "name" => "Rumor" }, "aliased_labels_attributes" => [ { "name" => "Market Talk" }, { "name" => "Speculation" } ] }
test.validate stuff

I get the following results. I'm not sure if this is "by design" / intentional or not. To me it just seems a little inconsistent and maybe a bit baffling as a new user

test.model.primary_label.name # nil
test.primary_label.model.name # nil
test.primary_label.name # "Rumor"

I would have expected all 3 to produce "Rumor", rather than a first two end up with nil. Especially given that test.model has all it's attributes set, it seems odd that items within nested properties do not set these details (I would have thought it would have been possible to do this lazily when you access test.primary_label.model - I'm not too sure what you'd do in the case of test.model.primary_label as this might be a trickier case to deal with.

Even if this "by design" I wonder if this is worth documenting along with the reasons/motivations?

The original reason I bring this up is also is that I'd like to come up with a generic way to be able to save objects when dealing with nesting, in the same way that accepts_nested_attributes_for is able to. My guess is that 80% of the time users will want to mimic accepts_nested_attributes_for as for the most part this is normally what is needed, and it's just a handful of times where a more fine control is needed (maybe a good idea to create some sort of "save policy" system to handle the bulk of generic cases when saving nested models). For the most part accepts_nested_attributes_for does the job for me apart from a few cases when it comes to deal with error handling (because errors in nested model objects shouldn't be handled by the top most / active / original callee model object.

On this note I think would would be handy would be a way to have some sort of utility method that returns child model objects from the nesting process in some sort of collection so it would be easy to iterate over and save them. I guess the other issue at hand here is how we deal with assigning foreign keys, such as making sure that tag_id is assigned when saving the labels. (Obviously this would need to support several layers / levels of nesting)

Plus for a large part, reform for me and I guess for a number of other users is seems to be more about having an alternative to StrongParams (which in my mind is an ugly solution), along with the ability to break down models / decorate models with specific validations/logic for specific contexts.

I did skim over the source and notice you have a model.save call as a default action when saving and have placed a comment to state that you plan to deal with case of dealing with nested model objects.

One to many: undefined method `from_hash' for nil:NilClass on validate

Validate seems to run fine if I have no nested models. But if I have a has_many relation with a appropriate collection call in the form I get the above error.

My form code:

class ProjectForm < Reform::Form
  model      :project, on: :project

  properties [:name, :number_of_buildings, :number_of_parking_slots], on: :project

  validates :name,
            presence:     true
  validates :number_of_buildings,
            presence:     true
  validates :number_of_parking_slots,
            presence:     true

  collection :buildings do
    model    :building, on: :building

    properties [:name, :number_of_floors, :number_of_apartments], on: :building

    validates :name,
              presence:     true
    validates :number_of_floors
              presence:     true
    validates :number_of_apartments,
              presence:     true
  end
end

And of course, a Project has_many Buildings. I am using Reform 0.2.0.

Am I doing something I am not supposed to, or did I stumble on a bug?

Question about form#save behavior with Rails

I'm experimenting with Reform, and really like parts of it so far. However, I'm a bit confused about the role of form.save as intended.

Ideally, I would like for it to map the form data to the provided objects and allow me to access those objects. So for example:

form = SongForm.new(Song.new)
form.validate(params)
form.save
form.song  # I thought it would returns the song object populated with params data, but object is not saved to the database.

However, when I try this in practice, calling form.song raises an error. Additionally, I see a note in the README that the function might (as of v0.2.1) call a database save. How do I go about accessing the model with assigned attributes from the form object?

undefined method `superclass' for Reform::Form::DSL:Module

I ran into this error after I updated the reform gem in this reform_example app and went to /users/new.

The error was:

undefined method `superclass' for Reform::Form::DSL:Module

I don't know how to fix it. Seems to have to do with the inheritable_attr :model_options bit.

uber (0.0.4) lib/uber/inheritable_attr.rb:16:in `inherit_for'
(eval):7:in `model_options'
bundler/gems/reform-830ed9deed9d/lib/reform/form/active_model.rb:78:in `model_name'
activemodel (4.0.0.rc1) lib/active_model/translation.rb:57:in `block in human_attribute_name'
activemodel (4.0.0.rc1) lib/active_model/translation.rb:56:in `map'
activemodel (4.0.0.rc1) lib/active_model/translation.rb:56:in `human_attribute_name'
actionpack (4.0.0.rc1) lib/action_view/helpers/tags/label.rb:54:in `render'
actionpack (4.0.0.rc1) lib/action_view/helpers/form_helper.rb:750:in `label'
actionpack (4.0.0.rc1) lib/action_view/helpers/form_helper.rb:1589:in `label'
app/views/users/_form.html.erb:3:in `block in _app_views_users__form_html_erb___232801701415194634_20247200'

Here's the relevant code from lib/reform/form/active_model.rb:

    def model_name
      if model_options
        form_name = model_options.first.to_s.camelize
      else
        form_name = name.sub(/Form$/, "")
      end

      active_model_name_for(form_name)
    end

  module ClassMethods
    # this module is only meant to extend (not include). # DISCUSS: is this a sustainable concept?
    def self.extended(base)
      base.class_eval do
        extend Uber::InheritableAttribute
        inheritable_attr :model_options
      end
    end

validates_uniqueness_of doesn't work with inline nested forms

class EmployeeForm < Reform::Form
  include Reform::Form::ActiveRecord

  property :user do
    property :email

    validates :email, presence: true
    validates_uniqueness_of :email
  end
end

Results in:

NoMethodError - undefined method `validates_uniqueness_of' for #<Class:0x007fc3c4e4bf08>:

I understand that validates_uniqueness_of comes from Reform::Form::ActiveRecord, so I tried including that in the nested form, like so:

class EmployeeForm < Reform::Form
  include Reform::Form::ActiveRecord

  property :user do
    include Reform::Form::ActiveRecord # <-- Trying to get uniqueness validator into here
    property :email

    validates :email, presence: true
    validates_uniqueness_of :email
  end
end

But that results in:

NoMethodError - undefined method `has_key?' for nil:NilClass:

So, now I'm stuck. I know this might work with v0.2.2's explicit form: UserForm, but I can't do that because of the issue I mentioned here: #47 (comment)

A simple solution would be to pass on all of the stuff in Reform::Form::ActiveRecord to nested forms.

Nested form issue: undefined method from_hash for nil

I haven't slept much, so probably I'm missing something obvious but I've been going around this for a couple hours to no avail.

class InvoiceForm < Reform::Form
  include Reform::Form::ActiveRecord

  property :date

  collection :line_items do
    property :description
    property :quantity
    property :price
  end
end

params = {"date"=>"2014-02-18", "line_items_attributes"=>
{"1392781994276"=>{"_destroy"=>"false", "description"=>"Manual entry", "quantity"=>"1", "price"=>"20"}}}

invoice = InvoiceForm.new(Invoice.new)
invoice.validate(params) # This throws the error on lib/representable/deserializer.rb:50

Right now I'm about to use a composition instead, to work around this issue, but I'm wondering what's the real problem.

exception raised when nested property is nil

The following code for me is triggering a big stacktrace with error NoMethodError: undefined method name' for nil:NilClass`

class Album < OpenStruct
end

class AlbumForm < Reform::Form
  property :artist do
    property :name
  end
end

AlbumForm.new(Album.new)

One way around this is to override the #initialize and ensure that the nested property (model.artist) exists, but I think that might be too brittle. I have a couple of ideas on how to possibly fix this:

  1. #initialize could call a new prepare_model method which subclasses of Reform::Form could override. Not a great deal better than overriding #initialize though.

  2. property method could support a :builder option that takes a block, or symbol containing a method name to invoke on the model (i.e. :build_artist in a has_one/belongs_to ActiveRecord relationship).

What are your thoughts?

Domain Validations Stay in Model?

just a thought: I am not 100 on this as an idea; but ideas are cool; I really like reforms approach on the form object arena.

in terms of validations; especially when it comes to uniqueness etc; the model should deal with those validations and not the form object? the form should only validate at the form level? no more require active_record mongoid variants on validates unique etc. so the form object still has validations but just ones that it can handle.

if model has been set on the form object then prehaps the validate and save can by default delegate down to the model itself; but you can opt out.

validate will have merged error messages from the model object; via calling valid? on the model; not sure I like that as the model will need populating hmmm

save will not only populate incoming data but will also call model.save ;again optional and if block given then most def dont call save on the model

in theory we can by default have

if form.validate({"email" => "[email protected]"}) && form.save
  # blar
end

Cheers

Using Reform for a search form

I tried to use reform as a form object for a search form, but in order to use reform I had to initialise it by passing an object to the constructor.

Is it possible to create a reform object with no parameters?

How to handle has_many relations?

I'm not sure whether if it is already supported and is missing docs or if it doesn't work yet but it would be pretty cool if we could handle one to many associations :-)

Use form class on property definition doesn't create the #<property>_attributes= method

Hi guys,

The new option to define a nested property passing a form object as option is awesome, the only problem that i found was that when you specify a property this way, your 'master' form doesn't create the #<property>_attributes= method necessary to render the form with simple_form or form helpers.

Here are examples of both cases and the output of a console test showing that the method is not defined:

class AddressForm < Reform::Form
  property :receiver do
    property :name
  end
  #property :receiver, form: ReceiverForm
end

screen shot 2013-12-12 at 10 05 18

class AddressForm < Reform::Form
  #property :receiver do
   # property :name
  #end
  property :receiver, form: ReceiverForm
end

screen shot 2013-12-12 at 10 07 16

I18n error translations for nested models aren't scoped properly

Consider the following:

class Employee < ActiveRecord::Base
  belongs_to :user
end
class User < ActiveRecord::Base
   has_one :employee
end
class EmployeeForm < Reform::Form
  include Reform::Form::ActiveRecord

  property :user do
    property :email
    validates :email, presence: true
  end
end

When I submitted my form w/ an empty email, I could not figure out what key ActiveModel was trying to use to get the 'errors' translations, so I monkey-patched it to spit it out the args to the log file. Here is what I found:

(Each line represents the *args of a single call to I18n.translate)

[:"activemodel.errors.models.anon_inline.attributes.email.blank", {:default=>[:"activemodel.errors.models.anon_inline.blank", :"activemodel.errors.models.reform/.attributes.email.blank", :"activemodel.errors.models.reform/.blank", :"activemodel.errors.messages.blank", :"errors.attributes.email.blank", :"errors.messages.blank"], :model=>"Anon inline", :attribute=>"Email", :value=>""}]
[:"activemodel.errors.models.anon_inline.blank", {:model=>"Anon inline", :attribute=>"Email", :value=>"", :locale=>:en, :throw=>true}]
[:"activemodel.errors.models.reform/.attributes.email.blank", {:model=>"Anon inline", :attribute=>"Email", :value=>"", :locale=>:en, :throw=>true}]
[:"activemodel.errors.models.reform/.blank", {:model=>"Anon inline", :attribute=>"Email", :value=>"", :locale=>:en, :throw=>true}]
[:"activemodel.errors.messages.blank", {:model=>"Anon inline", :attribute=>"Email", :value=>"", :locale=>:en, :throw=>true}]

Obvious workaround is just to pass the message to the validates call (or doing the translation myself), but this is tedious. Thanks for taking a look.

Updating @model after save

I'm finding I need to do this:

@record.save do |form, attributes|
  record = Record.new(attributes)
  record.user = current_user
  record.save!
  form.model = record # Should this be automatic?
end

So that I can use redirect_to with @record. I understand how Reform wants to know as little as possible about the persistence layer, but this is handy:

@record.save do |form, attributes|
  form.model = Record.new(attributes).tap do |record|
    record.user = current_user
    record.save!
  end
end

And I'm actually pushing that code to the form (with the proper adjustments), and the controller just calls @record.create(params[:record]). Hopefully this isn't too out of reform's philosophy? Or is there a better way?

Here's the full code when pushed to the form:

def create(params)
  return false unless validate(params)

  save do |form, attributes|
    form.model = Record.new(attributes).tap do |record|
      record.user = model.user
      record.save!
    end
  end

  true
end

Should I leave that to the controller? I kind of like how simple the actions end up being...

Just looking for some feedback while I'm getting used to this nifty gem :)

Coercion doesn't work inside collections

It seems the virtus coersions don't work inside collections - e.g:

class SomeForm < Reform::Form
  include Coercion

  property :birthday, type: Date

  collection :someobjects do
    property :somebool, type: Boolean
  end
end    

This fails with

uninitialized constant ProjectForm::Boolean

Strangely, it doesn't seem to affect all object types - String for example doesn't throw an error...

How to handle when Form property does not map to a model

how can I do this ? dont really want to pollute data model with accessors

the data model does not have password or password_confirmation but I want to validate at the form level and I dont want to populate the data model with these properties either; cant see any easy way; kind of get lost around the representer class etc.

the simplest way would be if I can just set a property but without an :on option. that would be really nice; it validates() but does not populate the model

thanks

require 'reform/form/coercion'

module RegistrationApp
  class RegistrationForm <  Reform::Form
    include DSL
    include Reform::Form::ActiveModel
    include Reform::Form::Coercion

    # model
    model :user

    property :email, on: :user, type: ::RegistrationApp::Virtus::StringDowncaseStrip
    property :password, on: :user
    property :password_confirmation, on: :user

    # valdiations
    validates :password, presence: true, confirmation: true, :length => { :in => 4..20 }, :allow_nil => true
    validates :email, presence: true, format: {with: RFC822::EMAIL}, :allow_nil => true

  end
end

Need Support for Form inheritance

Hi.
Here is it an example:

  class UserForm <  Reform::Form
    propery :name
    # more fields ,more logic
  end
  class CustomerForm < UserForm
    property :address
  end

the fields of CustomerForm will not contain UserForm's fields.
because of @representer_class is a class instance variable

Using collections when there are no items

if I create a form object (lets say for a user) and use the collection method (such that a user can have multiple permissions) then this will fail if the there are no items for the form's collection.

Basically it fails here if there are no permissions for the user form object:
params[attr.name] = params["#{attr.name}_attributes"].values
(Line 26 of reform (0.2.1) lib/reform/form/active_model.rb)

Because params["#{attr.name}_attributes"] will not exist rather than being an empty hash table.

collection seems to be assuming a 1..many relationship when really has_many in AR assumes a 0..many relationship

Undefined method for an ActiveRecord model

Hi,

I try to do a very simple thing. I have this form :

require 'reform/rails'

class OrderForm < Reform::Form
  include DSL
  include Reform::Form::ActiveRecord

  model :order, on: :order
end

I have this in my controller :

def new
  @form = OrderForm.new(order: Order.new)
end

And this in my view :

<%= simple_form_for(@form) do |f| %>
<% end %>

Very simple but I have this error :

undefined method `order' for #<#<Class:0x007fb2de576780>:0x007fb2dd6f3f28>

I tried to change my controller for this :

def new
  @form = OrderForm.new(order: Order.new)
  @form.order
end

And It's the same thing.

I tried with the master branch and the version '0.1.2'.

Did I missing something?

Thanks for you help and good job @apotonick!

Why is :on needed to access form.[model] ?

I really liked the (https://github.com/gogogarrett/reform_example)[example] @gogogarrett made, with separate form workflows. Wanted to replicate this but for a simpler case, only one model. Had some issues, I'll try to explain.

This is how it works:

class Users::UserProfilesController < ApplicationController
  ...
  def create_edit_form
    @form = Users::UserProfileForm.new(user: user)
  end
end

class Users::UserProfileForm < Reform::Form
  include DSL

  model :user, on: :user

  property :email, on: :user
  property :name, on: :user
end

class Users::UserProfileWorkflow
  ...
  def process
    ...
    form.save do |data, map|
      if form.user.new_record?
    ...
  end
end

So the idea is to pass in the form to workflow where it can further delegate saving to a service. The problematic part is form.user. Only with the above combination is it possible to get the user object from the form to check whether it is a new object or not.

So, this is what confuses me:

  • DSL in form, it is deprecated, but without it this doesn't work
  • in main readme usually only the plain model is passed to form when initialized, but here we have to use .new(user: user), I suppose this is because of the above included DSL
  • due to DSL, the on: :user is needed for all properties

I guess the question is can we relay on DSL, being deprecated?

Rails 4 - is it ready?

I'm asking because I'd like to use the gem in a new Rails 4 project.

My first attempts are unsuccessful - I'm getting an "undefined method `persisted?'" error...

Attributes are not filled on a composed form

Hello,

I have a simple form like this:

class CollectorRegistrationForm < Reform::Form
  include Composition

  properties [:profile, :email, :password, :password_confirmation,
              :remember_me, :name, :phone, :confirmed_at, :confirmation_token,
              :confirmation_sent_at, :first_name, :sex, :current_password],
              on: :user

  properties [:address, :postal_code, :city, :country, :country_id, :region,
              :region_id], on: :profile


  validates :postal_code, :city, :country, :name, :first_name, :sex, :phone,
    presence: true
end

When I do this:

profile = CollectorProfile.first
user = profile.user
form = CollectorRegistrationForm.new(user: user, profile: profile)
form.validate(first_name: nil) # return true
form.user.first_name # return the user's name (not nil)
user.first_name = nil
form = CollectorRegistrationForm.new(user: user, profile: profile)
form.validate(first_name: "test") # return false

Reform seems to not take the attributes in the validation's hash. Is there something I don't understand?

Thanks!

Issue with validations in Rails 4

Hi,

I'm trying to make the following form object work in a Rails 4 application.

require 'reform/rails'

class StudentDetailsForm < Reform::Form
  include DSL
  include Reform::Form::ActiveRecord

  property :firstname, on: :student

  model :student

  validates :firstname, presence: true
end

I'm having a hard time with validations:

f = StudentDetailsForm.new(student: Student.new)
#=> #<Reform::Fields firstname=nil>

f.validate(firstname: 'x')
#=> false

f.errors.full_messages
#=> ["Firstname can't be blank"]

It's a Rails 4 application but I don't know if it's related. Any idea what's wrong here?

Access to model(s) in getter

My particular use-case involves the rolify gem (https://github.com/EppO/rolify). I'm trying to create a virtual property on the form based on whether the user has a certain role on a certain resource. This is a simplified version:

# Form
class UserForm < Reform::Form
  include Reform::Form::ActiveRecord

  model :user

  def initialize(user, forum)
    super(user)
    @forum = forum
  end

  def user
    model
  end

  property :name
  property :is_admin, virtual: true, getter: lambda do |data|
    user.has_role?(:admin, @forum)
  end
end

I would then save the form with a save block:

# part of controller update method
...
@form.save do |data, nested|
  @form.user.name = data.name
  @form.user.save!

  if data.is_admin == '1'
    @form.user.add_role(:admin, @forum)
  else
    @form.user.remove_role(:admin, @forum)
  end
end
...

This is a specific example, but I imagine a main use case for getters would be to create virtual properties that combine instance information from the model with information outside of the model (if you just need information from a single model instance I think defining a method on the model with the same name as the form property will work with virtual: true without needing a getter?).

Is there currently any way to accomplish what I'm trying to do? The getter lambda does not seem to have access to any instance methods or properties on the form. It seems to get executed in the models context? I'm also not sure if the object that gets passed as an argument to the getter lambda could be of any help. As far as I can tell it's an Option object, which is basically a hash and it's empty?

I might be missing something obvious, I'm not yet that familiar with ruby. I was hoping someone here could point me in the right direction.

undefined method `model' for

Here is my form

class PersonalForm < Reform::Form
  include DSL
  include Reform::Form::ActiveRecord

  properties [:birhtdate, :email, :hour_rate, :location, :name, :site, :skype, :speciality, :status], on: :profile

  model :user, on: :user
end

When I trying to initialize form I gets undefined method `model' for error. Whats wrong?

Reform::Form::ActiveRecord is not automatically included?

The Readme says:

Reform provides the following ActiveRecord specific features. They're mixed in automatically in a Rails/AR setup.
Calling Form#save will explicitely call save on your model (added in 0.2.1) which will usually trigger a database insertion or update.

But it seems like you still have to manually include Reform::Form::ActiveRecord into your model in order to have those features (auto-saving).

Maybe that is the intended behavior, but if so, the Readme should be updated to say that you must include that module rather than "They're mixed in automatically".

Looking at https://github.com/apotonick/reform/blob/master/lib/reform/rails.rb, it looks like Reform::Form::ActiveModel and Reform::Form::ActiveModel::FormBuilderMethods are automatically mixed in, but (even though reform/form/active_record is required), Reform::Form::ActiveRecord is not.

Property aliasing doesn't work

I'll illustrate with a code example:

company = Company.new(name: 'Foo')

class CompanyForm < Reform::Form
  property :name, as: :title
end

form = CompanyForm.new(company)

form.title # => NoMethodError: undefined method `title'
form.name # => nil
form.send :fields # => #<Reform::Fields name=nil, title="Foo">

I'm using version 0.2.4.

Virtual Attributes for Compositions

Hi, I saw reform support Virtual Attributes now, a really helpful feature,but it seems not working with compositions, I tried to make it work, since I'm not good at writting test, and not known reform's code structure very well, please help me review what I'm done,
niedhui@4b15ec1 thanks

I18n scope problem

Hello, I'm a rails beginner, below is the code in my form class

errors.add(:nickname, :empty);

after form.validate(params[:xxx]), i output the errors.full_messages

the column name not read the i18n namespace activerecord correctly

the reason is Reform::Form class i18n_scope is :activemodel

2.0.0p247 :025 > HsrTicketForm.i18n_scope
 => :activemodel

I found out a solution, add the following code in my form class

class << self
    def i18n_scope
      :activerecord
    end
  end 

but i have to add this code in all of my form class, any better solutions? thanks.

Problem using forms outside controllers?

I have the following code that I am trying to use in my seeds.rb file when running bundle exec rake db:seed but it's not working. Whatever I run my seeds it's failing saying everything is nil and it is

# CODE USING REFORM
username = `whoami`.chomp
params = {
  username: username,
  email: "#{username}@example.com",
  birthdate: Date.new(1978, 07, 19),
  password: 'Abc123',
  password_confirmation: 'Abc123',
  first_name: 'User',
  last_name: 'Name',
  street: 'Black Jack Street 25',
  zip: '352 63',
  city: 'Game City',
  country: 'Free Spins',
  gender: 'Male'
}

require 'reform/rails'
begin
  form = PlayerForm.new(player: Player.new, address: Address.new)
  registration = PlayerWorkflow.new(form, params)
  registration.process
rescue => e
  p e.message, e.backtrace; Rails.logger.error { e }
end
pp form.errors
#<ActiveModel::Errors:0x007fe99d931898
 @base=
  #<Reform::Fields avatar=nil, birthdate=nil, username=nil, email=nil, password=nil, password_confirmation=nil, first_name=nil, last_name=nil, gender=nil, street=nil, zip=nil, city=nil, country=nil, mobile=nil>,
 @messages=
  {:birthdate=>["can't be blank", "is not a valid date"],
   :username=>["can't be blank"],
   :email=>["can't be blank", "is invalid"]}>
=> #<ActiveModel::Errors:0x007fe99d931898 @base=#<Reform::Fields avatar=nil, birthdate=nil, username=nil, email=nil, password=nil, password_confirmation=nil, first_name=nil, last_name=nil, gender=nil, street=nil, zip=nil, city=nil, country=nil, mobile=nil>, @messages={:birthdate=>["can't be blank", "is not a valid date"], :username=>["can't be blank"], :email=>["can't be blank", "is invalid"]}>

However when using that form with the exact same data from RSpec or browser it works fine. What is up with that?

Uniqueness validation doesn't work when using composition

First of all thanks for creating this gem - good work!

I'm having problems trying to use the uniqueness validation for a Reform form object. It works fine when the form represents a single model (e.g. User) but does not work when using composition (i.e. when the form represents multiple models). I'm using Rails 4.0.0 and Ruby 2.0.0p-247.

When the validate method is called on the form object I get an error like:

NoMethodError: undefined method `read_attribute_for_validation' for #<#<Class:0x007fb56c8fbfa0>:0x007fb56ea0dc48>

The following works fine:

class UserForm < Reform::Form
  include Reform::Form::ActiveRecord

  property :email

  validates :email, presence: true, uniqueness: true
end

The following does not:

class UserForm < Reform::Form
  include Reform::Form::ActiveRecord
  include Reform::Form::Composition

  model :user

  property :email,            on: :user
  property :name,             on: :profile
  property :date_of_birth,    on: :profile
  property :favorite_color,   on: :profile

  validates :email, presence: true, uniqueness: true
end

Here's a test:

require 'test_helper'

class UserFormTest < ActiveSupport::TestCase

  setup do
    @user = User.new
    @profile = UserProfile.new
    @form = UserForm.new(user: @user, profile: @profile)
  end

  def valid_params
    params = {'email'=>'[email protected]', 'name'=>'Bobert', 'date_of_birth'=>'Tue, 22 Oct 2000', 'favorite_color'=>'Green'}
  end

  test "fails validation if email blank" do
    params = valid_params.merge('email'=>'')
    refute @form.validate(params)
  end

  test "passes validation if all good" do
    assert @form.validate(valid_params)
  end

  test "requires email to be unique" do
    u = User.create!(email: '[email protected]')
    refute @form.validate(valid_params)
    assert @form.errors[:email].any?
  end

end

Running the tests gives errors:

# Running tests:

EEE

Finished tests in 0.084186s, 35.6354 tests/s, 0.0000 assertions/s.

  1) Error:
UserFormTest#test_fails_validation_if_email_blank:
NoMethodError: undefined method `read_attribute_for_validation' for #<#<Class:0x007fb56c8fbfa0>:0x007fb56c93b088>
    test/forms/user_form_test.rb:17:in `block in <class:UserFormTest>'

  2) Error:
UserFormTest#test_passes_validation_if_all_good:
NoMethodError: undefined method `read_attribute_for_validation' for #<#<Class:0x007fb56c8fbfa0>:0x007fb56e992840>
    test/forms/user_form_test.rb:21:in `block in <class:UserFormTest>'

  3) Error:
UserFormTest#test_requires_email_to_be_unique:
NoMethodError: undefined method `read_attribute_for_validation' for #<#<Class:0x007fb56c8fbfa0>:0x007fb56ea0dc48>
    test/forms/user_form_test.rb:26:in `block in <class:UserFormTest>'

Here lines 17, 21, 26 here refer to the lines in the tests where @form.validate(...) is called.

Thanks.

Ability to access and manipulate values inside of form object

When I am working inside of the form object that inherits from Reform::Form, is there any way to access the submitted values? Additionally is there a way to create a before_validation callback to manipulate form values. I am familiar with doing this in models but I was unable to get access to the form field values inside of the form object.

Bumping version please

@apotonick great gem, rolling out extensively, but I recently hit the inheritance issue #53 locally. After trawling through everything, I realise that whilst I am using the latest version 0.2.4, it does not include the fixes to enable inheritance.

Can you bump the version so that I don't have to lock my Gemfile into a commit?

Thanks.

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.