Please read for an updated status on RailsCasts:
Learn more or hide this
What do you think about using this technique?
I've analyze this, it's good.
You always seem to release just the right episode for the problem I'm trying to solve. Thanks for all the help over the years!
Thanks, this is really well done and helpful. I especially like that the whole process is a refactoring of old code. I've found it difficult to design loading, authorization, presenters, service_objects, etc. into the beginning of a complex project. It always seems to work better to refactor code as it becomes complex into a set of objects that work well together. Form objects must really work well with form builders and form presenters.
And it's good to do it that way, otherwise you start running into premature optimization.
Ryan, definitely have to agree with some of the others that you have a very uncanny knack for always going over issues I'm working on right now. I had a simple signup form that created an account and user which works but the code isn't as "elegant" as I would like. However, I think a form object would be more complex for my simple use case.
But this gets me thinking in that fashion; instead of throwing it all into one big object because it's all "related", I'll now be thinking about ways to separate logically-related chunks of code into separate objects. Did like the fact that you could "enforce" strong parameters by individually assigning values...it's something I innately knew but didn't fully realize until you pointed it out.
Really enjoy the casts on refactoring/organizing code. They help me a lot as I tend to write it for a deadline, and then maintain it later.
This is very appropriate for apps backed by web services where the app may not have a direct database connection. I'm currently working on such an app and the API serves JSON, so building form objects that are custom for the purpose is definitely the way to go. Thanks for showing us this technique.
Are form objects just as usable in edit actions?
Yes, Adam, it is.
I do like Form Objects, but all examples of it forget the edit action, which increases the complexity of the Form Object and decreases its beauty.
Your "edit" form object can often be a subclass of your "new"/"create" form object, that simply sets certain fields as read-only. Virtus, for one, makes that easy (where they're called private attributes).
Welcome back, Ryan!
I really enjoy refactoring videos, they are so full of content.
I get to see how you initially thought, how you improved it, how it ended up looking, and how that still fits the same slot as before.
Another vote for refactoring-themed videos in the future, here!
+1 to Refactoring videos :)
I have to disagree with some of this concept: Now the validations apply only to those forms, so if you somehow introduce another way to sign up (maybe an email gateway auto creates?) your validations do not run. I think in many cases it would be better to leave the the non form specific validations on the model and delegate them.
I think that in the best case, you would use a UserValidator class to perform general validations - this way it can be re-used by anything that needs to perform them.
Adding the ability to use a separate Validator class was a great change in Rails 3.. unfortunately I don't think it is used enough.
+1 this. I like the idea of form objects but I also like having the validations defined on the models to help make sure things like Rake tasks obey the business rules. I might try leaving the validations defined on the model and then having the form object delegate the valid? and errors() methods to the model.
+1 as well. I don't see why the pattern requires validations to be moved out of the model. If the validation keeps the persisted form of the data consistent, it should be part of any interaction with the model. If it is a validation that acts only on the form data, then I can see moving it into the form object. The password form object was a good example of that.
There's a really excellent pattern here, specifically I am thinking of a complicated sign-up process that is part of a single-page app. If you want to have a user fill in form data that goes to several models, the form model abstraction is perfect. Just make sure that it operates at the proper level of abstraction.
thanks for one more nice video!
I don't like to remove everything from Models and put it inside another class. At the end the Form Object's classes will do a lot.
I prefer to remove only the logic from the Model and keep all validations, callbacks, and stuffs like that there.
I forked your code and implement what I think is necessary to remove from Model: https://github.com/willian/416-form-objects/tree/master/signup-willian
What you think about it?
Don't forget to wrap up the save! in a transaction.
If something went wrong in the profile.save! the user must be rolledback.
Welcome back, Ryan!
if the user has a longer list of attributes, this might make the code cleaner:
[:original_password, :new_password, :new_password_confirmation].each do |attr|
I love railscasts, but not this episode. I think it is wrong that "testing is out of the scope of this episode". You should have had tests at the start of the refactoring and ran them instead of going to the browser all the time.
It is about the mind set.
A lot of times we need to get some tings up in a hurry, and skip testing (because it is out of the "scope of things").
I guess almost all rails developers in the world watch railscasts. So, railscasts has a responsibility to show the "right" work flow.
I understand that the episode is a bit long. But it would be better and shorter if you skipped the sign_up form and did TDD on the PasswordForm.
Sometimes I feel confusing when to use Service Object vs Form Object?
she is rough around the edges; but some of you might be interested in
I'm running into a problem. This is a good One to one nested form, but we usually do one to many nested forms.
I've had trouble using this because I can't assign multiple things. How would I deal with features like "link_to_add" or "link_to_remove" and assigning multiple, say salaries, to a player? Unlike nested forms(with simple form) doing f.input :amount passes in ONE value for the :amount since it does not recognize that there are multiple salaries.
I agree, I would like to see an example with more than one nested form, and eventually adding an arbitrary number of nested forms via js using a form object
+1 I'm looking for a sample as well. Would be really helpful to get this kind of example because I like the forms object approach that much.
Can anyone provide anything on this subject? I'm really looking myself.
Fairly simple to achieve.
Assuming your Player model has_many :salaries, you can enable the expected f.fields_for :salaries behaviour in your form by adding the appropriate attributes:
attr_accessor :salaries, :salaries_attributes
@player = player
@salaries = player.salaries
I can't believe how much pain this episode saved me this week. Using traditional methods was taking me down a twisted complex path that was dramatically simplified with a form object. This is right up there with presenters and service objects as the most significant changes in mindset for me.
Hi Ryan, Thanks for this great episode. In your submit code:
@user.password = new_password
I think you better use @user.save instead of save!. The reason is that your submit method can then only return true or false:
@user.password = new_password
As someone who reads your code, i dont expect to see user validation errors on password submit method.
Some notes on SignupForm. Each model has its own validations. When I want to use the same validations I should copy-past them?
Just a heads up, I'm adding this to a Rails 4 project and the params slice portion (e.g.)
event.attributes = params.slice(:name, :location, :about, :start, :end)
Is resulting in ActiveModel::ForbiddenAttributesError
Seems mass assignment protection might still need to be cared for even though I'm not delegating any security-critical properties.
In Rails 4 (and 4.1) you still need to use strong parameters and allow the properties in the controller. If you aren't at all worried about security you can just call params.permit! and that should work out just fine for you.
Assuming this is form for editing users, is there any good reason why I can't do what's below?
User.model_name #instead of ActiveModel::Name.new(self, nil, "User")
How can I deal with locales regarding form objects?
My labels and error messages don't use what's defined in the YAML like they do with normal active record models.
I would really like some input on this, since to work around it I had to bake my own solution and replace every validation with my own.
Include the error messages in your YAML under activemodel, not activerecord.
For my LookupForm form object, I've got some YAML that looks like this:
blank: "Needs a license plate"
blank: "Needs a state"
I'm trying to use Form Objects for something more complicated than what is in this railscast. I'm interacting with multiple models and in this case it doesn't make sense for me to move the validations from each model or to replicate them in the form object. I've tried using a validate do block and going through each object i'm creating, seeing if its valid? then adding the validation errors to the errors of the form, but I'm having a lot of trouble getting this to display properly. Has anyone else run into this issue?
Thanks for a great episode. You constantly amaze me with these ideas.
I think I found a small typo though. In /app/views/users/new.html.erb you have:
I think you mean
Thanks, that was just what i needed! Having faced design loader,service authorization and presenters.. This really helped me, i am going to recommend my peers to go through this episode too!
puts "awesome guy"
Hey. Thanks a lot for the timely info.
I want to to thank you for this very good read!! I certainly enjoyed
every little bit of it. I have you book-marked to check out new things you post…
wowowo i like it
Nice article! Thank you
super! I like this blog, thanks guys for your big deal!
nice post admin great work.
First sign in through GitHub to post a comment.