Form parameters not hashing correctly - ruby-on-rails

First, here is the code: http://pastie.org/5448967
Now, here is the scenario:
I have a project object - don't worry about the attachment batches as I have not implemented a solution for those yet - which has associated analysis requests of which there are set options of analyses. That is, there is a table of analyses which the user can choose from and for each one chosen an analysis request is created that links the analysis to the project.
Here's the problem:
When attempting to update the analysis requests the first thing that I notice is a problem with the parameters from the form not "hashing" as I would expect.
Here's what I want:
params={:projects=>{"#{project_id}"=>{project_attribute, project_attribute, etc..., analysisrequests=>[array,of,analysis,ids]}, "#{project_id}"=>{project_attribute, project_attribute, etc..., analysisrequests=>[array,of,analysis,ids]}, ...}
Here's what I get:
{"utf8"=>"✓", "_method"=>"put", "authenticity_token"=>"XfIr7zfVNdlg5HS3Letw4sI/MGNFTtqntQYrgjAh9TY=", "project"=>{"1832"=>{"project_number"=>"261", "nof_samples"=>"5", "analysisrequest"=>{"analysis_id"=>["", "8",
"12"]}}, "status_id"=>"13", "rush_service"=>"0", "customer_id"=>"111", "project_notes"=>"test", "sample_desc"=>"test", "user_id"=>"1", "quoted_price"=>"1", "1834"=>{"project_number"=>"777", "nof_samples"=>"1", "analysisrequest"=>{"analysis_id"=>["", "3", "9", "20", "22"]}}}, "commit"=>"Submit Changes"}
Stating the obvious:
I'm not too worried about changing "analysisrequest"=>{"analysis_id"=>["", "8", ...]} to "analysisrequest"=>["", "8", ...] or "analysisrequests"=>[] (note the s). In fact, I've done this so as to render a single multi-select in the form rather than a multi-select for each request associated with the project (which it does if you add the 's') - the "analysis_id" nesting is fine as well as you can see that each request could have "analysisrequest"=>{"analysis_id"=>[],"project_id"=>[]} so that's obviously Rails doing its job.
The most important of my concerns is that the additional parameters seen after the analysis requests in the first project in the hash are not appearing after the second. That, and, that these additional parameters are not within the hash keyed by "#{project_id}" and, are, instead, outside of it in their own little world. This makes it very difficult for me to update by doing something like:
params[:projects].keys.each do |project_id|
'update entries based on project_id'
end
Is there anyone out there who might be able to give me some expert advice on this matter? I'm willing to exchange my IT or chemical services for your advice (I work in a lab) because I need to get this up and working.
Thanks for reading,
-Adam
tl;dr - Form is not "hashing" the attributes in the form in the way that I think it should.
--EDITS--
11/29/2012 - I've still been unable to understand why the parameters hash is being skewed the way it is but, I've come up with a reasonable workaround if I can figure out how to do one thing: configure a delete option via formtastic or my own methods. That is, already through the creation form for a project am I able to create as many analysis requests as needed it's just that now I would need a way to add and remove them through the edit form. I can see an option within the collection being set to "Delete" with the database id working with a method at the controller level; however, what is more pressing is being able to add additional requests in the form which I have not yet been able to achieve via Rails 3.2.1. I know that it works in 2.3.5 but, so far, it hasn't been working for my configuration. I'll post more as I find out.
+2 hours (later) - Implemented Ryan Bates' ep.197 methods for removing fields so far; but, I've run into some kind of gotcha where either formtastic or my model has no idea what to do with the _destroy field even though I set the :allow_destroy => true. Perhaps it has to do with the placement of the link? Here's the code for my newest form (the js and application helper code are as they can be seen here): http://pastie.org/5454653

Check out this: http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
Perhaps you should have these routes:
resources :projects do
collection do
get 'edit', action: 'edit_collection'
put 'update_collection'
end
end
Don't forget to define a update_collection method in the controller.
Just a note you can use class instances in edit_collection.html.erb instead of rendering the page and passing in the local.
For instance:
<% #projects.each do |project| %>
instead of
<% for project in projects %>

Related

Don't understand when a ROR View will/will not have a param for a model

I am having difficulty understanding how to cause a View in ROR4 to have a param for a model. It shows up when I try to use strong parameters in a controller, specifically:
def model1_params
params.require(:model1).permit(:attr1,attr2)
end
Sometimes it works. Sometimes I get "param not found: model1"
My understanding is that it fails (when it does) because the web page being submitted doesn't have any param called "model1" and that it works (when it does) because something I have done has caused the web page submission to have this param. In the latter case, the param is a hash representing the fields of the model, with members for attr1, attr2, etc.
Does it work when I use "form_for" (which specifies a model) but not the other kind of form call, which is not tied to a model?
Note: I am writing because I've had this problem for several weeks with no progress. I have searched the Rails doc and lots of instructional examples on the web but with no luck. I know I'm being dense, but so far I've not found a real API reference document, nor a real API programmer's guide that covers strong parameters.
I've found lots of examples on strong parameters but none of them clearly explain how it works, that would let me figure out on my own what I am doing wrong.
Say your controller's method is associated to a route model1_controller_method_path.
Your method can be called from a link in a view, and if the model1 parameter is not present, yes, you will get a 'param not found' error. To avoid this, make sure to have your parameter passed from your link_to as in:
link_to model1_controller_method_path(:model1 => the_value_you_want)

How would I setup routing and partial loading for a rails application with a central dashboard?

I am having trouble figuring out how to make my rails app work with a central dashboard. Basically I want it to work so that when a user clicks on a course from the list, the details of the course are loaded into the dashboard. The ajax part isn't too complicated, I am however struggling slightly with loading the partial in when ajax isn't being used. I figure I can simply put some logic in the view so that if #course != null it will load the partial.
The routes are also giving me a bit of difficulty. So I wanted the url to resemble the following: dashboard/student/:id/university/:id/course/:id
I came up with the following, but is there a better way?
http://pastebin.com/keEmaznw
It's hard to understand your question. You've given us scant detail, and insufficient code. That said, regarding loading a partial with a nil object:
If you pass an object to a partial, Rails will take care of the rendering for you automatically, and no nil (instead of null, in Ruby talk) check is necessary:
= render #object
Regarding routes, yes, there is a better way: You should generally avoid nesting your routes more than two levels. This becomes a nightmare to test, and you end up writing very long method names:
edit_dashboard_student_university_course_path(#university, #course, #student)
You really don't want to be writing that in your view or your tests. think about it, if you want to edit a student, is it necessary to load the university and the course as well?

Creating links using link_to for hierarchical model

Okay, so I am new to Rails and am creating a project management system with the framework. For a couple of my models, views, and controllers I used scaffolding and had no problems. For other parts, I coded all of the parts myself.
So as an overview of my project, at the root of it all you can have many projects. Within all of these projects you can create multiple to-do lists. Within each to-do list, you can have multiple tasks. This is what I meant by "hierarchical" in the title.
I just created my lists page, and when I go to the URL directly in my browser (ex: http://localhost:3000/projects/3/lists/20/tasks/1) the task is displayed correctly. However, I do not know how to format my link that is in one of my to-do list views (the tasks are usually shown below the to-do list but now I want them to show on their own view).
Here is the code I currently have:
<%= link_to "#{task.description}", project_list_tasks_url(#list.id,task.id) %>
I know that the link_to "#{task.description}" is correct because I tried using it with a static URL (Google or something), but project_list_tasks_url(#list.id,task.id) is where I'm having trouble.
Can anybody help me out? I can provide as much code as you'd like from my to-do list or task controllers and views.
A couple of tips to help make routing less confusing. It can be a bit unnerving to get used to.
Routing Rule #1
Always check the output of rake routes to be sure how to call your various routing methods. You might think you know how your routes will play out by looking at routes.rb but you won't know until you look at the compiled routes table.
In your case you're expecting a route with the format:
/projects/:project_id/lists/:list_id/tasks/:id
Be sure that's the case. If it is, your call should look like:
project_list_task_path(#project, #list, task)
Note that the arguments here are :project_id, :list_id and :id, so all three are required in this case. Any in brackets in the path specification can be ignored, like :format usually is.
Routing Rule #2
Use the _path methods unless you strictly require the full URL. They're shorter and the output is easier to read and debug. They also don't inadvertently flip the URL in the browser and cause session problems if you don't properly differentiate between www.mysite.com and site.com.
Routing Rule #3
Don't forget there's a huge difference between #project and #project.id when it's supplied to a routing path method.
The router will always call the to_param method if it's available and this can be over-ridden in your model to produce pretty or friendly URLs. id is for your database and your database alone. to_param is for routing but you shouldn't be calling it manually unless you're doing something exceptionally irregular.
You generally should not nest resources more than one level deep, but putting that aside, the link_to format should be:
link_to task.description, project_list_task_path(#project, #list, task)
i.e. project_link_tasks_url should be project_link_task_url, and you have to pass the #project as the first argument (I'm assuming that your project is named #project). I've switched _url to _path so you can just pass the objects themselves as arguments rather than their ids.
See the documentation on creating paths and URLs from objects for details.

Use multiple checkboxes to change one field - rails controller change params

I've got articles that can be shown in lots of different sites. They can either be visible or not.
I've ended up going for a single bitmasked permission field in the article, rather than lots of has_many permissions separate records.
I'm not sure how best to set this field. What I've done so far is write two methods in the article model - one gives you a hash of {1 => 'true', 2 => 'true', 3 => 'false'} - visible or not on site 1, 2, 3. The second method takes a similar hash and sets the permission field correctly.
I can send the permission hash to my view through the controller, and I can make checkboxes that show if the article is visible or not. These appear on a pop up dialog using jquery. I haven't done it yet, but I think I can use javascript to make a hash to send back.
But I don't know how to make the update controller take the hash from the params, send it to my make permission method and then put that into the params again to update my article.
How would I go about doing this? Or am I barking up the wrong tree entirely.
Any ideas?
I would suggest you to create a Site model which reproduces the different sites. This is especially a good thing if there might come up more websites! Then you could build a has_and_belongs_to_many association between the Site and the Article model to commit on which site an article should be displayed!

How to use Rails named route helpers with parameters?

given this route
match 'posts/hello/:name/:title' => 'post#show', :as => :hello
what are the ways that I can call hello_path ?
if i call hello_path(#post), what does it try to do?
I was hoping that the :name and :title files will bind to the path automatically but it seems that rails only know how to get the :id out of the model object.
instead, it only works if I call it like
<%= link_to "link2", hello_url(:name=> #post.name, :title=>#post.title) %>
(lack of proper documentation is really killing me)
To answer your two questions:
At the command line, runrake routes to see what routes there are in
your app. It will show you all the ways you can use the named routes,
just add "_path" or "_url" to the name of the route which is shown on
the left.
Calling hello_path(#post) will generate a URL to the
show page for that hello instance.
The way you are calling it is the norm:
<%= link_to "link2", hello_url(:name=> #post.name, :title=>#post.title) %>
However, this may work too:
<%= link_to "link2", hello_url(#post.name, #post.title) %>
Here is some documentation (other than the Rails API) which should help.
http://guides.rubyonrails.org/routing.html
To answer your question of "what does hello_path try to do?"
hello_path knows how many parameters it's supposed to get. This is from counting the named parameters in config/routes. It will accept either a hash or a list of arguments. If you give it a hash, the keys must match the names of the URL parameters. If you give it a list of arguments, it'll just match them up by position - the first argument with the first named parameter.
Then, it will call to_param on each parameter individually before joining them all together (see code here, 4.0 branch).
If you pass in an object when it's expecting 2 or more params, it won't even get around to calling to_param on the object. That's when you get errors with no stack trace that say something like
No route matches {:controller=>"posts", :action=>"show", :id=>#<Post ...>}
Working with 1 named parameter
If you've only got one named parameter, things are pretty straightforward. If you need to look up your posts by name instead of id, you can just redefine to_param
class Post < ActiveRecord::Base
...
def to_param
name
end
end
Working with multiple named parameters
But if the URL has more than one named parameter in it, then redefining to_param isn't enough. Let's say you tried this:
# app/models/post.rb
class Post < ActiveRecord::Base
...
def to_param
{name: name, title: title}
end
end
# app/views/posts/index.html.erb
<%= post_path(post) %>
In this case, you'll get a routing error because you're not passing in enough arguments to post_path (see above). To get around this, I just call to_param explicitly:
# app/views/posts/index.html.erb
<%= post_path(post.to_param) %>
This is a little less slick than most Rails routing magic, but works perfectly well. If you later change the way you're looking up Posts, all you have to do is redefine to_param. No need to worry about all the places you've called post_path
Under the hood
The relevant code to look at is actionpack/lib/action_dispatch/routing
The other answers (at the time of this writing) are fine, but your reply to GregT's answer shows a lack of understand about Rails, which is fine -- we've all been there.
Specifically, three of the key principles behind Rails: convention over configuration, the model-view-controller architecture (MVC), and REST. It's stuff at the beginning of every beginning Rails book. Beginners often think they can just jump to the first chapter with code, but Rails differs from many other topics in that the first chapters explain important concepts and aren't just intro chapter filler. Because Rails isn't "code", it's a "framework of code".
"Convention over configuration" means if you follow certain conventions then you benefit from behaviors baked into Rails. Routing is one of those areas, if not the biggest, where convention benefits the developer although it is entirely configurable.
Paths following a specific routing format, are parsed into the controller, action and possibly an id, format, and additional parameters. By default and at minimum, a Rails (and also a Sinatra) path takes the following format and order:
/controller_name/action_name
It's a little more complicated than that, with more options, and it looks more like this in actuality:
/controller_name/action_name/(id)(.format)(?param=value)(&...)
...but it's more detail than is necessary for this answer.
The controller is the C in MVC, or the class that handles the request. The action is one of the seven RESTful actions (index, show, new, create, edit, update, and destroy) within that controller. Not all actions require an id (index, new and create) and not all of them are get requests (requests that generate a view, for instance destroy, create and update don't have views).
Putting it all together we see this example:
/articles/edit/1
...will route the request to the 'edit' action in the ArticlesController controller passing along id 1. It's expected that the controller will do some housekeeping, like authentication and authorization, then retrieve Article model (MCV) with ID 1 and pass it along to the "edit" view (MCV).
It's best, especially for new Rails developers, to stick to these conventions and perhaps add a few additional actions beyond those provided by REST.
You can step outside this convention by configuring in your routes.rb file an alternative routing scheme, not following REST, etc., but you'll be like a salmon swimming upstream -- it's a lot harder than going with the flow. If you go down that path (pun) you'll make a lot of additional work for yourself, probably reinvent the wheel somewhat, and lose advantages provided by the Rails framework. If you find yourself in that situation for whatever reason, perhaps Rails isn't the right tool for your job.
You can also call it like this
hello_path(#post.name, #post.title)
Hope it helps.

Resources