new/edit actions on show page :: best practice question - ruby-on-rails

i am designing a dashboard page that summarizes data from multiple models, all on one page. and i also want to include forms on that page for creating new items.
after some experimenting, i realized i needed some advice as to the best practice for this situation... here are the 2 methods i was attempting
1) using nested field_fors...
form_for #job do |j|
j.fields_for :invoice do |i|
i.fields_for :charges do |c|
c.text_field :amount
or 2)
form_for #job.invoice.charges.build do |c|
c.text_field :amount
with the nested approach, the problem is now i have a new object which throws a wrench in my show view when it iterates through existing objects. suddenly trying to render all the created_at times fails since the new object hasn't been saved yet.
the problem with approach 2) is either i post each form to its own controller to handle it, but if it doesnt validate, i need to render the show view... which means i have to provide each controller i have a form for with all the instance variables and methods etc to render the page.
and i havent quite figured out how the error handling works. sometimes my form is marked with fieldWitherrors class, and sometimes it doesnt. i have been trying out both approaches but am having trouble figuring it out.
can anyone offer advice on the direction i should go here?

If new charge is all you need, then the latter approach is the way. Note that these do basically the same:
form_for #job.invoice.charges.build do |c|
and
#charge = Charge.new(:invoice_id => #job.invoice.id)
form_for #charge do |c|
Using this second case you don't connect the new charge with the job until it's saved and if you move the first line with #charge=... to a controller, you can re-render the form when validations fail.

Related

Is there a real problem with using i.e. get method instead of put in an action through clicking on a button

For example I have button for thumbs up or I want to resend a specific mail or I change a specific enum state through clicking on a button (like published/unpublished)
Now regarding the rails implementation, I can make it work using either put, get or patch, since I only have to send a specific id.
In my opinion it seems to be best practice using patch, post or put wheter one or several attributes will change on my objects. So this seems mainly to be a convention here.
On the server side I will have to add some policies to allow only specific users to do so, but beyond adding policies are there any possible issues with not using the conventional http-method here?
One very real problem with using GET is that is supposed to be an idempotent method. Lets say the new guy creates the form:
get '/things/create', to: "things#create"
<%= form_with(model: #post, url: '/posts/create', method: :get) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<%= f.submit %>
<% end %>
class PostsController < ApplicationController
def index
#posts = Post.all
end
def create
#post = Post.new(title: params[:post][:title])
#post.save
redirect_to action: :index
end
end
He then tries it out in the browser in and is perfectly happy with it. Mission accomplished.
His boss then attempts to test it. He creates a Post titled "This new guy might not be so bad anyways". And then he hits the back button to try creating another Post. Weirdly it just loops back to the index page. He tries it again - the only thing that happens is that the page starts to fill up with "This new guy might not be so bad anyways" and he is becomes less and less convinced that its actually true.
If you used POST, PATCH, PUT or DELETE the browser would have warned him that he is about to resend a form. Thats why GET should never change anything on the server (besides maybe your pageview stats).
It also opens up for any malicous actor to get users to create, delete or modify resources simply by fooling them into clicking a link. The malicous actor doesn't even have to got though the effort of creating a phishing site and circumventing the anti-CSRF protection that Rails provides.
There is absolutely no difference between how POST, PATCH, PUT or DELETE are treated by the client or server beyond the conventions of Rails.
But since Rails is a highly convention driven framework which adheres to a specific flavor of REST it really befits you to follow those conventions if you want to be productive and not be that new guy.
When it comes to actions beyond the classical CRUD verbs its really down to your best judgement and intent is really what matters. What does the action do? Is it updating something (PATCH)? Is it actually a separate resource? (POST /mails/1/mailouts). As you may see there is no easy answer. Just be clear and document what you're doing if you're unsure.

Update and create multiple records in a single form in Rails 3

I have followed railscast #198 Edit Multiple Individually and it works 100% for editing multiple records from a single form.
However, I would like to also create new records from the same form if the record. i.e. in the controller assess the hash of params from the form, update those records that exist and create the records that don't exist.
Also, if any validation errors occur they should be caught and passed back.
This in the controller works and will update correctly if the fields for the new record aren't present
#costs = IngredientCost.update(params[:ingredient_costs].keys, params[:ingredient_costs].values).reject { |item| item.errors.empty? }
For the fields for the new records I am currently creating an artibrary large unique number as the record id (I'm not sure if this is right to do) and then the message comes back that it cannot be saved because the id doesn't exist, which is to be expected as I it doesn't exist.
I've tried the following two snippets as a stab in the dark but not really sure which way I should be going..
#costs = IngredientCost.find_or_initialize_by_id(:id => params[:ingredient_costs].keys).update(params[:ingredient_costs].keys, params[:ingredient_costs].values).reject { |i| i.errors.empty? }
and
params[:ingredient_costs].each do |ingredient_cost|
#cost = IngredientCost.find_or_initialize_by_ingredient_id_and_user_id(:ingredient_id => ingredient_cost[1]["ingredient_id"], :user_id => current_user.id)
#cost = IngredientCost.update(:ingredient_id => params[:ingredient_costs][ingredient_cost[0]]["ingredient_id"], :quantity => params[:ingredient_costs][ingredient_cost[0]]["quantity"], :price_per_unit => params[:ingredient_costs][ingredient_cost[0]]["price_per_unit"], :weight_id => params[:ingredient_costs][ingredient_cost[0]]["weight_id"])
Neither seem to work.
I've seen this work before but for updating/creating nested attributes from one form. I don't really want to have to go that route with this.
I eventually resorted to saving the multiple records through a parent model which already had the correct associations.
I followed Railscast #196 Nested Model Forms which was fairly similar to above only saving to another model and placing accepts_nested_attributes_for in that model's *.rb.
Remember to move the routes.
Move the controller code to the receiving action of the parent model.
My form in the viewer now looks like this (in haml format):
= form_for #user, :remote => true do |f|
= f.fields_for :ingredient_costs do |builder|
// fields inserted here
It took me a couple hours to realise I had to use builder.object... to get to the child that I actually am interested. The .object class wasn't obvious to me.
Anyway, hope this was useful to someone. I think SybariteManoj's answer looks like a good deal, but I couldn't get it to work.
You can try following snippet:
#costs = []
params[:ingredient_costs].each do |ingredient_cost|
if ingredient_cost[:id].blank?
cost = IngredientCost.create(ingredient_cost)
#costs << cost
else
cost = IngredientCost.find(ingredient_cost[:id])
cost.update_attributes(ingredient_cost)
#costs << cost
end
end
P.S. I haven't tested it yet. But for the re-rendering the edit page with the errors, the #costs variable has the data with errors if any.

Is there a more elegant way? (accessing a rails controller from a static home page?)

OK, this is working but I feel there is a better way to do this in Rails... I have a home page which, if you have not signed in, is not currently pulling in anything from any model or controller. It exists at /pages/home.html.erb
On that page, I want to grab the next party from my Parties model and tell the website visitor about that party. Easy enough, right?:
/app/controllers/parties_controller.rb
def nextparty
#party = Party.find(:first, :order => "begins_on")
end
Now, in my home page, I used this and it works fine:
/app/views/pages/home.html.erb
<% #PartyCont = PartiesController.new() %>
<% #party = #PartyCont.nextparty() %>
<h3>The next party is <%= #party.name %></h3>
I tried helper methods, partials, ApplicationHelper, but this was the only code that actually worked. Most of the other things I tried seemed to fail because the #Party class was not instantiated (typically the error indicated the class with a temporary name and "undefined method").
Hey, I'm happy that it works, but I feel like there is a better way in Rails. I've seen a few posts that use code like the above example and then say "But you really shouldn't ever need to do this!".
Is this just fine, or is there a more Rails-like way?
UPDATE:
I think the problem is more than just elegance... I just realized that all RSPEC tests that hit the home page are failing with:
Failure/Error: get 'home'
ActionView::Template::Error:
undefined method `begins_on' for nil:NilClass
Thanks!
You want a controller behind every view and you don't want views crossing controller boundaries in order to present information. Consider having a welcome controller (or whatever you prefer to call it). It can have an index action:
def index
#party = Party.find(:first, :order => "begins_on")
end
In config/routes.rb, make it the root controller action:
root :to => "welcome#index"
Also, to DRY that up add a .nextparty class method to the Party model and call that from both of your controller actions instead of the find method.
Your view should only show data that already was made available by your controller. You want to display a party resource, so the request should go to the parties controller. If I understand your use case correctly, than more specifically to the index method on the PartiesController.
There you should have the following code:
def index
#party = Party.find(:first, :order => "begins_on")
end
That instance method will be available in your corresponding view app/views/parties/index.html.erb
<h3>The next party is <%= #party.name %></h3>
To make this available as your homepage you will have to adjust your route as well:
config/routes.rb
root :to => "parties#index"
Your view should contain as little logic as possible and mainly be concerned with how things look.
Your controller should get data for the view ready and make sure to call the right method on the model.
All the heavy business logic should be in your model.
I think you should work through a basic introductory Rails tutorial.

Attaching onClick event to Rails's link_to_function

How do I attach an onclick event to a link_to_function such that clicking on the event refreshes an element on the page (using partials)?
When the user clicks the generated link, I'd like to refresh the partial containing the code so that i gets updated.
def add_step_link(form_builder)
logger.info 'ADD_STEP_LINK'
link_to_function 'add a step' do |page|
form_builder.fields_for :steps, Step.new, :child_index => 'NEW_RECORD' do |f|
logger.info 'inserted js'
html = render(:partial => 'step', :locals => { :step_form => f, :i=>#i+=1})
page << "$('steps').insert({ bottom: '#{escape_javascript(html)}'.replace(/NEW_RECORD/g, new Date().getTime()) });"
end
end
end
I have a partial in a larger form that contains just:
<%= add_step_link(technique_form) %>
I am attempting keeping track of the number of steps in a technique. In the form I am creating, users can add new steps to a set of instructions. Right now, I have default fields for steps 1-7. Adding one step, gets you step 8. The problem is that subsequent steps are numbered '8' also.
I am extending the "Multiple child models in a dynamic form" tutorial in http://railsforum.com/viewtopic.php?id=28447 for my own purposes.
Ok, I'm a little confused about what you are doing, but I'm going to try and make some suggestions.
Rather than use link_to_function, use link_to_remote. Link to function calls javascript, but what you actually want to do is call back to a controller that then runs some rjs to either replace the full partial or, more likely, append the new partial (containing the step) to the end of your current steps.
This is similar to all those examples you will have seen where people append a comment to the end of their blog comments (expect using link_to_remote rather than remote_form_for) see 3/4 of the way through http://media.rubyonrails.org/video/rails_blog_2.mov
You can see the docs for link_to_remote here: http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper.html
I suggest using link_to_remote as RichH suggests. The trick for you it seems is keeping track of #i. I'd either put it as a URL parameter in link_to_remote, or in the session object. I'd suggest the session—it seems easier.

Adding a photo to a model's has_many model from a view

I have a model, Thing, that has a has_many with ThingPhoto, using Paperclip to manage everything. On the "show" view for Thing, I want to have a file upload, and have it relate to the Thing model.
For some reason, I'm totally glitching on how this should be done. I've tried doing this (Haml):
- form_for #thing.thing_photos, :html => {:multipart => true} do |f|
= f.file_field :photo
= f.submit
... and I get this error:
undefined method `array_path' for #<ActionView::Base:0x24d42b4>
Google is failing me. I'm sure this is super easy, but I just can't get my brain around it.
Edit: I should have mentioned that if I change the #thing.thing_photos to just #thing, it works fine, in that it displays the form, but of course it's not associated with the correct model.
try #thing.thing_photos.new
this should instance a new model of thing_photos. If you're not using nested routes you'll have to add the thing_id as a hidden field.
If you're not tied to Paperclip (and if you're just doing images), I've had a lot of luck with fleximage. It's got really good documentation for image upload, manipulation, and display (with all sorts of neat effects like watermarking and stuff).
It doesn't quite answer your question, but it might be a cool thing to look into.
But I bet your problem related to the fact you're creating a form for an array of objects (through an association). I bet something is finding the type of the object (should be "thing_photo" but is "array" in your example), and appending "path" to it to write the form post URL. Perhaps you should add a .first or create a partial?
The form should be for #thing_photo and should either pass in the thing_id or if it's a nested route you can get it from that
then in your thing_photos controller
#thing_photo = Photo.new(params[:thing_photo])
#if using nested route
#thing = Thing.find(params[:thing_id])
#if using hidden field
#thing = Thing.find(params[:thing_photo][:thing_id])
if #thing_photo.save
#this is what ties the thing photo to the thing
#thing.thing_photos << #thing_photo
end
If I understand the question correctly, this might help.

Resources