Processing multiple controller parameters into a single model attribute? - ruby-on-rails

EDIT
It turns out I misunderstood how the #date_select form helper method works — I arrived at the approach below based on this answer, but after some tinkering, I discovered that, in typical Rails fashion, #date_select actually handles the whole thing automagically.
In any case, there are still times when I'd like to do something similar: for instance, if I want to capture a single User.tenure attribute as a serialized hash ({ start: <year>, end: <year> }), and I implement this in the view form as two separate fields that return the parameters :tenure_start and :tenure_end.
Where would the logic go to convert these two parameters into a single model attribute?
My problem, specifically
I want to have a "date-of-birth" attribute for the User model in my application. The New User form uses Rails' built-in #date_select form helper method.
In the view, #date_select creates three separate drop-down menus for year, month, and day. In the controller, it returns three separate parameters: dob(1i), dob(2i), and dob(3i). Each of these parameters is a string representation of its respective field (e.g., if you select 1992/7/14, the parameter values are "1992", "07", and "14").
I want to convert these three string parameters into a single Date object and assign it to a new User.
The question, generally
My understanding of MVC is that the controller should be thin: its job is merely to pass information between the model and the view. But in the above case, there are controller parameters that don't have corresponding model attributes. Thus,
User.create(user_params)
doesn't work, because User::new doesn't accept the :"dob(1i)" attribute (and wouldn't know what to do with it if it did). They need to be processed into a single value with something like
dob = params.fetch(:player).values_at(%i(dob(1i) dob(2i) dob(3i))).map(&:to_i)
params.fetch(:player)[:dob] = { player: { dob: Date.new(dob[0], dob[1], dob[2]) } }
before they can be saved into the dob attribute.
My question is this: How much of this logic belongs in the controller, and how much belongs in the model?
Should the controller be left to handle conversion of the input, and instantiate the model with exactly the data it needs?
or should the controller simply concatenate the three fields into a single string (e.g., "19920714") and leave it to the model to convert it to a Date before saving?
or is there a way to pass the three raw, unprocessed parameters into a model object and have it handle the conversion?
or is there another possibility I haven't thought of?

You can do something like this to store actual date in database
year = "1992"
month = "14"
day = "07"
dob = m + '/' + d + '/' + y
=> Date.parse(dob)
=> Tue, 14 Jul 1992

Related

Html.ValidationMessageFor( MyKey ) without being tiedto a model's attribute?

My model does not really represent what my form is posting. Example my Orgs Model which holds orgs helps me generate a treeview the users selects several nodes of the orgs tree and submits a form. The form posts an array[] or org ids.
(maybe i'm doing this all wrong, please let me know tried binding to models and that was confusing when dealing with trees grids etc and using partial views and ajax returning partial views and editorfor's etc.. the default model binding was useless)
anyways back to my point, since I want to validate if any orgs get selected:
if (SelectedOrgs == null) //array[]
{
ModelState.AddModelError("OrgsNotSelected",IValidationErrors.OrgsNotSelected);
}
my question is how do i retrieve this random key that i just made up from my view? my model and even my viewmodel do not have an array for the selection this is just the result of the post.
I'm not sure what to do in the view to get the value for "OrgsNotSelected".
Thank you!
Bilal
If you were doing normal submit actions to your controller, you would need to use the ValidationSummary to display errors that are not attached to a specific property.
As you are using Ajax, you would be better off returning a json result from your controller and you can define this so that it includes your errors in a format you can use in the success function to display your messages.

ModelState.IsValid is false because CreatedBy field is null

I have an entity with, amongst others, the fields: CreatedBy, CreatedAt, ChangedAt
Now in the view, I do not want the user to fill these fields.
My approach was to fill them on the HTTP POST Action, before ModelState.IsValid check is made and before saving the data to the database.
However, ModelState.IsValid keeps returning false no matter what. What is the right way to implement this? Should I take the validation (ModelState.IsValid) from the POST action?
One problem, many solutions (from best to worst, in my opinion).
First solution
The best way would be to use a ViewModel (a class containing only the fields which must be edited by user and / or validated).
In this case, the CreatedBy, CreatedAt, ChangedAt fields would not appear in your ViewModel class.
Second solution
If you don't want ViewModel, you can put the "not mutable by user" fields as hidden fields in your view
#Html.HiddenFor(m => m.CreatedAt)
of course, hidden fields can be changed by user, so it's always an open door to unwanted datas...
Third solution
Clean ModelState from undesired errors before checking if it's valid (should become really boring if you've many fields concerned).
if (ModelState.ContainsKey("CreatedAt")) {
ModelState["CreatedAt"].Errors.Clear();
}
//then test for ModelState.IsValid()
Other solutions
WhiteList, BlackList for model binding, or... anything I forgot !
Many projects like to separate the View Model from the Domain Model. This allows you to create a View-Model class specifically tailored for the data you want to render and/or receive in a certain action while keeping the domain model correct/consistent.
In your View-Model class you would either not define any such property as the created date (since it is not supposed to be posted but is determined in the action). Or if you use one and the same View-Model for rendering and posting, and you want to render the date, you can make the date nullable (see Alexey's answer) on the View-Model while keeping it mandatory on the domain model.
DateTime is not nullable. If you want to keep those fields as null during model binding, if there is no values for them use:
Nullable<DateTime>
or shortcut:
DateTime?

How to create multiple domain objects from a GSP page

I have a Person class with two properties: name and address. I want to build a GSP page which allows for 10 users to be created at one time. This is how I'm implementing it and was wondering if there is a better way:
First, make 20 text boxes in the GSP page - 10 with someperson.name and 10 with someperson.address field names (make these in a loop or code them all individually, doesn't matter).
Second, process the submitted data in the controller. The someperson object has the submitted data, but in a not-so-nice structure ([name: ['Bob', 'John'], address: ['Address 1', 'Address 2']]), so I call transpose() on this to be able to access name, address pairs.
Then, build a list of Person objects using the pairs obtained from the previous step and validate/save them.
Finally, if validation fails (name cannot be null) then do something... don't know what yet! I'm thinking of passing the collection of Person objects to the GSP where they are iterated using a loop and if hasErrors then show them... Don't know how to highlight the fields which failed validation...
So, is there a better way (I should probably ask WHAT IS the better way)?
You should use Grails' data-binding support by declaring a command object like this
class PersonCommand {
List<Person> people = []
}
If you construct your form so that the request parameters are named like this:
person[0].name=bob
person[0].address=england
person[1].name=john
person[1].address=ireland
The data will be automatically bound to the personCommand argument of this controller action
class MyController {
def savePeople = {PersonCommand personCommand->
}
}
If you call personCommand.validate() it might in turn call validate() on each Person in people (I'm not sure). If it doesn't you can do this yourself by calling
boolean allPersonsValid = personCommand.people.every {it.validate()}
At this point you'll know whether all Person instances are valid. If they are not, you should pass the PersonCommand back to the GSP and you can use the Grails tags:
<g:eachError>
<g:hasErrors>
<g:renderErrors>
to highlight the fields in errors. If you're not exactly sure how to use these tags to do the highlight, I suggest you run grails generate-all for a domain class and look at the GSP code it generates.

Instance variable in rails - apart from views where can we use it and for how long is it available

I have created a instance variable in rails project, which gets its value from a url parameter like example.com/value. This variable is created in new action, now can it also be used in create action, of the same model.
The value is a id of another model altogether and both the models are associated, I need to create the instance variable in former model.
I need to know, for how long the instance variable is available, and can be use the instance variable of one model in another model.
Clarification with real example
Supposingly there are two models, one is User model and other is Referral model. The root is root :to => 'users#new. Now the user will coming here via example.com/value, where value is the id for Referral model. Now using this value I have to increment two fields: One is visits, which shows how many visits did that particular url bring. Other is signup, which will increment if there is a signup using that value.
I have passed this value via routes in users#new, which I use to increment the visits column of Referral model. Now if the users signup, the users#create would be executed, and I want to be able to use the value in the create action as well, to increment the signup column in Referral model.
As of now, I understand that the instance variable I created in new action to store the value cannot be used in create action. Now how can I achieve this.
In general instance variables only last as long as the user's HTTP request, so they can not be created in one action and used in another.
You could try storing the variable in the session, a hidden input field on the HTML form generated by the new action, or in the urls of links generated by the new action.
I don't know exactly what you are doing, but from the names of your two actions it sounds like there is probably an HTML form involved, so I think the best thing is to use a hidden input, something like this:
<input type="hidden" name="model_id" value="<%= #model_id %>" />
Instance variables only last for that call and in the class they are defined, with the exception of the views. If you have a controller with two methods where one method is your route and another is used internally, then it will be available to both, it is also available to your views.
e.g.
test_controller.rb
def index
something_else
p #variable #outputs "foo" in the terminal
end
def something_else
#variable = "foo"
end
However it would not be available between create and new as these would be called in different requests.

How to pass an unpersisted modified object from view back to controller without a form?

Short: how does modelbinding pass objects from view to controller?
Long:
First, based on the parameters given by the user through a search form, some objects are retrieved from the database.
These objects are given meta data that are visible(but not defining) to the customer (e.g: naming and pricing of the objects differ from region to region).
Later on in the site, the user can click links that should show details of these objects.
Because these meta data are important for displaying, but not defining, I need to get the previously altered object back in the controller.
When I use the default asp.net mvc modelbinding, the .ToString() method is used. This off course doesn't return a relevant string for recreating the complete object.
I would have figured the ISerializable interface would be involved, but this is not so.
How should I go about to get the desired effect? I can't imagine I'm the first one to be faced with this question, so I guess I'm missing something somewhere...
The default model binding takes form parameters by name and matches them up with the properties of the type specified in the argument list. For example, your model has properties "Price" and "Name", then the form would need to contain inputs with ids/names "Price" and "Name" (I suspect it does a case insensitive match). The binder uses reflection to convert the form values associated with these keys into the appropriate type and assigns it to the properties of a newly created object of the type specified by the parameter (again derived by reflection).
You can actually look at (and download) the source for this at http://www.codeplex.com/aspnet, although you'll have to drill down into the MVC source from there. I'd give a link to the DefaultModelBinder source, but the way they are constructed, I believe the link changes as revisions are introduced.
So, to answer your question, you need to have parameters (could be hidden) on your form that correspond to the properties of the object that you want to recreate. When you POST the form (in the view) to the controller, the binder should reconstitute an object of the specified type using the form parameters. If you need to do translation from the values in the form parameter to the object properties, you'll probably need to implement your own custom model binder.
[EDIT] In response to your second post:
Let's say that we want to have a link back to an action that uses a customized object. We can store the customized object in TempData (or the Session if we need it to last more through more than one postback) with a particular key. We can then construct the action link and provide the key of the object as value to the ActionLink in an anonymous class. This will pass back the key as a Request parameter. In our action we can use the key from this parameter to retrieve the object from TempData.
<%= Html.ActionLink( ViewData["CustomObject1",
"Select",
new { TempDataKey = ViewData["CustomObject1_Key"] }
) %>
public ActionResult Select()
{
Entity custObj = null;
string objKey = Request.Params["TempDataKey"];
if (!string.IsNullOrEmpty(objKey))
{
custObj = (Entity)TempData[objKey];
}
... continue processing
}
#tvanfosson
Thanks for your explanation, but what about links? (no forms involved)
Currently the Html.ActionLink(c=>c.Action(parameter), "label") takes objects as parameter. These have to be translated into URL parts. For this, MVC ALWAYS goes to the .ToString() method. I don't want to serialize my object in the ToString method.
Shouldn't I be able to somehow help the framework serialize my object? Say through the ISerialize interface or something?

Resources