I have "quick add" form that is responsible for creating a Foobar. Once it submits/saves, you are returned to the form to add the next Foobar. I now have polymorphic Tasks that I'm wanting to add to the Foobar as it's being created. The Tasks require no details in this context, so I've opted to have a checkbox on the form that says "Do This Task". If checked when the form is submitted, I'd like to have a Task created and associated to the Foobar.
Doing it isn't hard, but I'm struggling to find what seems to be the "right" way of doing it.
Option 1: The Foobar accepts_nested_attributes_for Tasks so I can create a nested form, but there's really nothing to nest. The checkbox doesn't represent a valid field on the Task, only that I want the task created. I could use #foobar.build_task and put some hidden fields in the form for the task, but I'd have to use JS voodoo to prevent the fields from submitting if the checkbox isn't checked. This just seems dirty and wrong.
Option 2: I can put some logic in FoobarController#create that looks for the checkbox and builds the task onto the Foobar before save. The issue here is that the Task is polymorphic and could be tied to other things, too. There may even be multiple "types" of tasks tied to the single Foobar being created. This solution is better than Option 1, I think, but isn't very DRY. Any such logic in the controller would end up being duplicated in the controllers for other taskable things.
Option 3: Have a before_save on Foobar that looks for the presence of a pseudo-field called "create_task" and then builds the task. This moves the duplication out of controllers and into taskable models, but it's no more a duplication than the duplicated "has_many :tasks" line in each model. Still, it doesn't seem like it's the model's job to look for such a field and act on it.
so.... I'd really appreciate some thoughts.
UPDATE #1: A little extra info...
A Task also has a creator and an assignee, both are users in the system. A creator should be automatically assigned based on a current_user method, which of course is available in the View and Controller, but not the model. I think the excludes right away the possibility of the answer being purely model based (such as option 3). I think it also hints at the answer not being view based (such as option 1), as the user should be set server side where it can't be tampered with. So perhaps the answer lies somewhere in the Controller? Perhaps some sort of helper method to wrap up the logic to be duplicated in each Controller that handles taskable things?
UPDATE #2: My current leaning...
I spent some time speaking with a respected developer friend and am further convinced the answer is closest to option 2. Both the view and model seem wrong. The controller makes sense, but the main problem is the potential of repeated code. I think the answer is going to be in finding the best way to factor out the controller code that handles the attachment of Tasks (or other polymorphic things like comments, file uploads, etc.) to the object that controller is responsible for. When I have a solution I'm happy with, I'll try and post it here. Thanks for the input, everyone!
I would go with something that is a derivative of Option 1. There are actually two solution I might use though.
Solution 1
This one is much simpler but isn't quite as extensible.
Using the existing accepts_nested_attributes_for :tasks, make sure you have this option though:
accepts_nested_attributes_for :tasks, :allow_destory => true
Then in your in your form use (assuming task_collection is a collection of prebuilt tasks):
<%= form_for(#record) do |form| %>
...
<%= form.fields_for :tasks, task_collection do |task_fields| %>
<%= task_fields.check_box(:_destroy,{},0,1) %> Do This Task
<% end %>
...
<% end %>
Basically this is going to take Task association-object and if the box is checked persist it, otherwise mark it for destruction. This should prevent any so called "JS voodoo" since any such logic is handled in the model.
Solution 2
The other option, but the much more complex option, I might use is to:
def tasks_attributes=(tasks_attributes)
Doing that in each applicable model(or an included module) then handling all the tasks attributes when they're submitted from the form.
It involves handling the task_attributes in the model. which means you have to build your forms a certain way and some other subtle details. But using this approach you wouldn't have to build a Task object then destroy it, you could just handle the submission data to create tasks.
The details of doing this is pretty in-depth and not something I really want to go into. But you seem resourceful enough that just knowing it's there could help you out.
More info on this is in the fields_for documentation and the accepts_attributes_for implementation.
In this situation I would go like this:
class Foobar
has_many :tasks
attr_accessor :add_task
after_create :create_task, if: add_task == true
private
def create_task
tasks.create(...)
end
end
Related
I'm in the process of updating a website I made almost 2 years ago. It was my first real website and I made some mistakes (some more serious that others).
What apparently is one of my biggest is making database calls from the view.
I'm pretty damn sure there is a better way to do this:
Use Case:
Someone fills out a form for a new subject, populating the Subject table, and they have been marked "enrolled", Subject.enrolled = 1
Based on that, I now need to create a record in 5 other tables (such as Baseline)
Downhill from here, here is my method
Determine if the record exist based on subject_id from Subject (sub)
<$ if Baseline.where(subject_id: sub.subject_id).first != nil $>
If it does not exist, create the record, (otherwise display the link)
<%= Baseline.create(subject_id: sub.subject_id) %>
This all happens in the view, and creates a front-end table with links to each record in the process. So I'm creating records based on for-loop logic...
Question:
So I'm looking for direction. I don't want to guess how to do this - I'm pretty sure the model/controller should do this - I want to learn how to do it correctly. How do I create records automatically, based on a value in a table?
Thank you for your time.
Not quite sure how your domain and code looks like, but to answer this question: 'How do I create records automatically, based on a value in a table?', it seems that you could use ActiveRecord callbacks, like this:
class Subject < ActiveRecord::Base
after_commit :create_baseline_if_enrolled, on: [:create, :update]
private
def create_baseline_if_enrolled
return unless enrolled?
# enrolled? == true, you may create these models here
end
end
To answer your question:
It depends :) This is just one possible solution. Another one would be to put such a custom logic in your SubjectsController and call it directly from the #create, #update methods. Both approaches have pros and cons. For example, abusing callbacks (anywhere) makes code less readable and harder to debug. On the other hand, putting such logic in controllers puts a burden on you that you have to remember about calling it if you happen to be editing subjects in other places (but is more explicit). Whichever way you choose, remember not to make your classes too fat, for example try to use service object pattern to separate such custom logic as soon as you feel like it is getting out of hand. :) And don't forget about tests - when things go wrong, tests make refactoring easier.
I have a collection of records that are related to a specific parent object. I have no need to update the parent, just want to update all the children.
I tried making a Reform::Form and simply adding the collection declaration. An example might be a developer has many projects. When the developer goes on holiday his projects are "on_hold", but some of them might still be active. So, I would like to list all the projects with a checkbox to check if they should be put on hold. I essentially want to have a simple_fields_for :projects.
Does it make sense to use Reform for this?
I have this code:
class ProjectsForm < Reform::Form
collection :projects do
property :name #perhaps I want to rename them in the same form (bear with me)
property :on_hold
end
end
This should work, but when initializing the form with a hash
#form = ProjectsForm.new({projects: #array_of_projects})
I get an error
undefined method `projects' for #<Hash:0x007fce8f2783b8>
As if the collection method is not working. I am obviously doing something stupid.
I'd really like to use Reform. I love the philosophy behind a lot of the trailblazing suit of gems. It will be great if someone can point me in the right direction here.
If it turns out this isn't a good usecase for Reform I'll find another :P
Update:
I think reform is slightly more coupled with the idea of a model than what I thought. I thought it was just a form object with properties to play nicely with form builder. I now find that your model is key. You need to initialize the Reform form with A model, or in the case of composition, a few models. However, if you pass in an array of hash reform believes this is the model and then tries to access the :projects property on the model. In my case a hash.
I have now added a
# my contrived example is getting lame
attr_accessor :projects
to the developer class and it is working. At least the generating the reform object is working.
I am still curious wether this is a good use-case for Reform.
I'm pretty much a Rails beginner, at the moment developing a rather complex webapp in Rails 2.2.
So, in this webapp, there are "factories". Not the design pattern, mind you, actual factories (it's a game).
For each factory in a player's team, there are a bunch of options he can take.
Level of maintenance for the machinery
Rewards for workers
etc.
I have two problems with this:
These are all checkboxes, and the checkbox needs to be defaulted with the answer already stored in the db. What I mean is the check must be already in the box if the player already checked it (sorry if this passage is confusing).
This information is stored in multiple tables. The level of maintenance in a factory is saved in one table, while the performance bonus given to workers is in another one.
Can anyone help me with this? This is all way over my head.
Thanks all.
To understand question 1, keep it simple and pretend the attributes you want to access are in the factories table. The design pattern then is pretty simple:
Database schema:
create_table :factories do |t|
t.boolean :now_operating, :default => true, :null => false
...
Controller:
def edit
#factory = Factory.find(params[:id])
if request.post?
#factory.update_attributes(params[:factory])
end
end
View:
<% form_for :factory, #factory, :url => { :action => "edit" } do |f| %>
<%= f.checkbox :now_operating %>
...
You can do this with less code, in fact with none at all if you use RESTful resources and follow naming conventions, but this is a little too much magic to start off, because it conceals what rails is doing for you.
Namely: when the :get action loads an object matching the object name passed to form_for, rails will populate the form fields with that object's attribute values from the database. Then, when the client submits the form, rails encodes those fields into the request as params for that object, which you can then update in the :post action. See the official Rails Guide for a more detailed treatment.
I apologize if that was a little too basic, but it's important to get the basic pattern because the general approach to solving problem 2 is the same:
Load the objects in your :get action.
Render a form with the objects enclosed.
Save the params for each object.
There are several ways of actually doing this for forms with multiple objects. I suggest you see the various tutorials listed under Item 8, Building Complex Forms, in the Rails Guide.
Two last points to consider:
Do you really need those extra tables? If each maintenance row
contains attributes for a single
factory, it may make more sense to
just put the attributes in the
factory table and simplify your forms
and actions.
Attributes such as :maintenance_level might be more
appropriately represented as a
:string type with a radio_button in
the form to set the level.
if the child entities (level of maintenance, performace, etc) are linked to the Factory, then you can use nested model forms.
You can check some info about nested forms here, or just google "rails nested forms" and you'll get plenty of blog posts and tutorials.
--EDIT
Seeing that you added that you're using rails 2.2, you could try to use simple_forms or formtastic to create the nested forms (this is just a suggestion as I have no idea if it works). And I can only thing of setting the values manually to update the models.
I have three models that can all have a Review and each review must belong to a User. I am using polymorphic associations :as => :reviewable but I am not sure where I should put the logic for creating a review for each model. I would assume that the CRUD for each review should be handled by the reviews_controller (or nested attributes? in the other controllers?), but then how do I associate the review with each model ?
I couldn't find any examples of model/view/controller on google, maybe that would help a lot in clearing out things.
Thanks,
Cezar
The problem with handling the object within the reviewable objects' controllers is that you'll find you wind up repeating a lot of the same code in each of the controllers. Moreover, where do you put the code? Either you wind up with some seriously heavy action methods (by piggy backing on your 7 existing actions) or you start creating actions like
new_review, delete_review, etc
which is not RESTful and is a serious code smell begging for its own controller.
Therefore, for something like this I'd consider having a review controller and either adding parameters to the url or using hidden fields to indicate what object the review belongs to:
<%= f.hidden_field :reviewable_id, :value => #object.id %>
<%= f.hidden_field :reviewable_type, :value => #object.class %>
This is particularly useful if there is any other work that needs to be done on the review object (such as save the current user as the author, etc.), as it keeps all the logic that can't be pushed out to the model in one place. Just be sure to scope the associated model correctly and ensure it belongs to the user, otherwise users can tamper with the URL and add reviews to whatever they want (of course that applies anywhere you select an object from user submitted data).
However, if the review object is very light-weight and doesn't require any extra processing (ie just a simple assignment of the posted data to the object) then you might want to take a look at using Rails 2.3 and it's nested object support.
Since it's an update to the model, it's easier to go through the model's controller. Plus then there is no mistaking which model the review belongs to.
Take a look at what acts_as_commentable does. It might be easier to repurpose than to roll your own.
http://github.com/jackdempsey/acts_as_commentable/tree/master
I'm looking for guidelines on when to know when a RESTful approach to a model and it's associations is proper, and when it's not. Perhaps it's the nature of my current application, but I'm finding that simple models with no associations work well with REST, but complex models with many has_many assocations really seem to complicate the view and the setup required in the controller. form_for calls start to become especially complicated.
Or perhaps it's my neophyte understanding. I've been doing Rails for over three years now, but REST and form helpers together seem to mystify me.
Make a resource of each top-level model in your system. By top-level, I mean models that are independent and have meaning outside of the associated model. Generally, that's most models. In the following example Position and Candidate are top-level. You could consider Candidate to be composed of PastEmployment and positions to which she has applied. Applications to positions and prior work history can be accessed through the Candidate resource, since they don't exist on their own.
Models
class Position
has_many :candidate_positions
has_many :candidates, :through => :candidate_positions
end
class Candidate
has_many :candidate_positions
has_many :positions, :through => :candidate_positions
has_many :past_employments
accepts_nested_attributes_for :past_employments
accepts_nested_attributes_for :candidate_positions
end
class PastEmployment
belongs_to :candidate
end
class CandidatePosition
belongs_to :candidate
belongs_to :position
end
Routes
map.resources :positions
map.resources :candidates
Use a non-resourceful controller for interactions with the user that span models. For example, if you wanted to have a HomeController that shows available positions as well as recent candidates, that would be a new, plain controller. If you want to edit any of the information on this controller, cool! You already have controllers available to handle the form posts, that will automatically be wired with <% form_for #candidate %>. You can render your collection of positions with <%= render #positions %>, and because you've made them a resource, Rails will know to look in views/positions/_position.html.erb for the appropriate partial.
The end result should be that you're never writing logic to handle the persistence of an object in more than one place. That's when controllers get complex and forms get out of hand. It also means that Rails and external systems know where to retrieve and store objects. Same URL, same controller, just a different format.
You might check out this series of Railscasts, which talks about has-many relationships and forms in the context of REST:
Complex Forms Part 1
Complex Forms Part 2
Complex Forms Part 3
I don't know RoR, so I will make generate statements on REST.
REST and ROI treats the URLs as series of nouns, and it uses HTTP methods like GET, POST, PUT, and DELETE as the verbs. This model works for CRUD, and even for models with multiple associations. Since URLs represent resources, associated objects can be expressed as list of URLs.
However if your system requires more fine-grain verbs like validate, move, copy, honk, read, write etc. it may not suit REST.
disclaimer: I know rails, but I still am pretty much a newbie.
Short Answer: REST and form helpers are completely different areas.
Long answer:
As I understand it,Representational State Transfer is only loosely related to the actual rendering of forms and views.
REST really has to do with controllers, and to a certain extend models. The idea is that instead of trying to think about an entire conversation with a client, you write a webapp to respond in specific, predictable ways to individual client messages.
i.e., if a client GETs a model, you just retrieve it, format it for them, send it to them, and forget about it.
if a client POSTS an update of some sort, you change the webapps state to reflect that, send back any response, and then forget about it. Any future GET or POST will look at the new state, but not the message that created it.
So, really, whether or not an application is RESTful depends not really on how complicated the model is, but on how users interact with it. An app meant to be at least somewhat client-agnostic, that is data-centric, is a good candidate for REST. Something that relies heavily on sessions, and interacting with a specific user, and is process-centric, might not be such a good candidate.
On the other hand, you have Rails form helpers. These are great for scaffolding, but sometimes can be frustrating when you try to use them in more complicated ways.
So, what is your main question? Do you have a specific question about rails form helpers? about rails controllers? or something specific to REST?