Rails has_one belongs_to Routing and form_for - ruby-on-rails

So I have two models
class First < ActiveRecord::Base
belongs_to :story
end
class Story < ActiveRecord::Base
has_one :first
end
I want to create a new first, which is associated with a story. Each story can only have one first. I'm trying to use this as my form_for
<%= form_for ([#story, #first]) do |f| %>
<%= f.label :message %><br/>
<%= f.text_field :message %><br/>
<%= f.submit %>
<% end %>
However, I don't know how to set this up in my routes to cater for this. At the moment, I get a "undefined method story_firsts_path" error. Here is my firsts controller
class FirstsController < ApplicationController
def new
#story = Story.new
#first = #story.build_first
end
end
Am I way off here or am I somewhat on the right track?
Thanks!

You should use nested routes to define story and first. Like this:
resources :stories do
resources :firsts
end

Related

Rails 4 Nested Forms Simple_form_for & simple_field_for

I'm just trying to generate a simple nested form, like so:
<%= simple_form_for #profile do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.input :phone_number %>
<%= f.simple_fields_for :addresses do |p| %>
<%= p.input :street %>
<%= p.input :city %>
<%= p.input :state, collection: us_states %>
<%= p.input :zip_code %>
<% end %>
<%= f.button :submit %>
My models:
class Profile < ActiveRecord::Base
belongs_to :customer
has_many :addresses
accepts_nested_attributes_for :addresses
end
class Address < ActiveRecord::Base
belongs_to :profile
end
My controller:
class ProfilesController < ApplicationController
before_action :authenticate_customer!
def new
#profile = Profile.new
end
end
Unfortunately that nested attribute addresses doesn't populate anything on the page, I would expect to see fields like street or city but I get nothing.
However, if I change <%= f.simple_fields_for :addresses do |p| %> to <%= f.simple_fields_for :address do |p| %> the fields display correctly.
Unfortunately doing this causes issues because I can't use the accepts_nested_attributes_for helper as outlined in the docs (as far as I can tell). Any idea why this isn't working?
The reason is because nested forms require created objects to work. It looks like Profile gets instantiated but Address does not.
class ProfilesController < ApplicationController
before_action :authenticate_customer!
def new
#profile = Profile.new
#profile.addresses.create # this will create the address object that the nested form will use
end
end
I think you will need to create Profile as well rather than create an instance of it.
#profile = Profile.create
I've just been working with nested forms myself and this is how it worked for me.
The solution was to build the profile and the addresses in the #new action for it to work. Revised working code:
class ProfilesController < ApplicationController
before_action :authenticate_customer!
def new
#profile = current_customer.build_profile
#profile.addresses.build
end
end
You'll need to look at how your params come through, but since I have a has_many, they came through hashed with a key of a record id.

Rails: How do I submit multiple objects into strong params?

I am making a goal tracking app. Right now outcome, purpose, action, priority, resources, and direction are all things which are part of Outcome in the database. However, I want to make purpose and action their own model objects. What I am confused about is how do I submit Outcome, Purpose, and Action, which will be 3 separate model objects, in a single HTTP request?
Should I just use multiple strong params in my controller?
app/view/outcomes/new.html.erb
You need to have model associations of outcomes with purpose and action.
Then you will need to create nested form. So that outform form can wrap purpose and action model attributes.
As you want to have different models for actions and purposes, I'm assuming outcome can has_many purposes and has_many actions. As per this type of association, below is the code you should have.
Your form will become something like:
<%= form_for #outcome do |f| %>
<%= f.label :outcome, "Outcome" %>
<%= f.text_area :outcome %>
<%= f.fields_for :purpose, #outcome.purpose.build do |p| %>
<%= p.text_area :desc, label: "Purpose" %>
<% end %>
<%= f.fields_for :action, #outcome.action.build do |p| %>
<%= p.text_area :desc, label: "Action" %>
<% end %>
<%= f.submit "submit" %>
<% end %>
Models:
# outcome.rb
has_many :purposes, :dependent => :destroy
has_many :actions, :dependent => :destroy
accepts_nested_attributes_of :purposes, :actions
-----------------------------------------
# purpose.rb
belongs_to :outcome
-----------------------------------------
# action.rb
belongs_to :outcome
Controller:
# outcomes_controller.rb
def outcome_params
params.require(:outcome).permit(:outcome, purpose_attributes:[:desc], action_attributes: [:desc])
end
SUGGESTION: You should rename your action model name to avoid unwanted conflicts with rails keyword action.
This may help you
Nestd Attributes
If the objects are associated (as below), you'll be best using the accepts_nested_attributes_for method:
#app/models/outcome.rb
Class Outcome < ActiveRecord::Base
has_many :purposes
has_many :actions
accepts_nested_attributes_for :purposes, :actions
end
#app/models/purpose.rb
Class Purpose < ActiveRecord::Base
belongs_to :outcome
end
#app/models/action.rb
Class Action < ActiveRecord::Base
belongs_to :outcome
end
accepts_nested_attributes_for means you'll be able to send the associated objects through the Outcome model - meaning you can send them all in a single HTTP request
You have to remember the way Rails is set up (MVC pattern), meaning if you send a single request; any further model objects you have will be able to be stored too.
Here's how you can set it up:
#app/controllers/outcomes_controller.rb
Class OutcomesController < ApplicationController
def new
#outcome = Outcome.new
#outcome.purposes.build
#outcoe.actions.build
end
def create
#outcome = Outcome.new(outcome_params)
#outcome.save
end
private
def outcome_params
params.require(:outcome).permit(:outcome, purpose_attributes:[:purpose], action_attributes: [:action])
end
end
Which will give you the ability to use this form:
#app/views/outcomes/new.html.erb
<%= form_for #outcome do |f| %>
<%= f.label :outcome %>
<%= f.text_area :outcome %>
<%= f.fields_for :purposes do |p| %>
<%= p.text_area :purpose %>
<% end %>
<%= f.fields_for :actions do |a| %>
<%= a.text_area :action %>
<% end %>
<%= f.submit %>
<% end %>
--
Recommendation
From the looks of it, I'd recommend you'll be able to keep all of these details in a single model - storing in multiple models seems overkill

Call create action through a form with a variable from another controller

Two of the models I have in the rails 4 app are the following:
class Council < ActiveRecord::Base
has_many :alternatives
...
end
class Alternative < ActiveRecord::Base
belongs_to :council
...
end
I am rendering an Alternative form that allows me to create a new Alternative object from a Council's show view:
councils/show.html.erb
<%= render 'alternatives/form' %>
alternatives/_form.html.erb
<%= form_for(#alternative) do |f| %>
<div class="form-group">
<div>
<%= f.text_field :title, :placeholder => 'Provide your alternative', autofocus: true, class:"form-control" %>
</div>
<div>
<%= f.text_area :more_info, :placeholder => 'Describe your alternative', autofocus: true, class:"form-control", rows: '4' %>
</div>
</div>
<div>
<%= f.submit 'Submit the alternative!', class:"btn btn-success" %>
</div>
<% end %>
At that point, I want to associate the Alternative object with the specific Council object from the show view, like the code below, but the variable #council is not defined:
controllers/alternatives_controller.rb
class AlternativesController < ApplicationController
before_action :set_alternative, only: [:show, :edit, :update, :destroy]
def create
#alternative = Alternative.new(alternative_params)
#alternative.council = #council
end
private
def set_alternative
#alternative = Alternative.find(params[:id])
end
def alternative_params
params.require(:alternative).permit(:title, :more_info)
end
end
That will allow me to show all the alternatives associated with a certain Council object:
councils/show.html.erb
...
<% #council.alternatives.each do |alternative| %>
<%= alternative.title %>
<%= alternative.more_info %>
<% end %>
...
I've carefully reviewed the Ruby on Rails Guides (http://guides.rubyonrails.org/association_basics.html#belongs-to-association-reference) but clearly I am missing something. Any ideas? Thank you.
Several options for you:
Nested
I've not tested this, but you could use multiple objects in your form_for helper:
#app/views/councils/show.html.erb
<%= form_for [#council, #alternative] do |f| %>
...
<% end %>
This will send your request through to the council_alternatives_path (you can change it), and provide you with params[:council_id] and params[:id]
You could set your routes like this to get it to work:
#config/routes.rb
resources :councils do
resources :alternatives, only: [:create]
end
This will be a little hacky I think (as it's meant for forms with nested attributes), but it's still a viable way to achieve what you want
Nested Attributes
The other option is to use the accepts_nested_attributes_for directive in your model. This will be overkill for what you need I think, but could help:
#app/models/council.rb
Class Council < ActiveRecord::Base
has_many :alternatives
accepts_nested_attributes_for :alternatives
end
#app/models/alternative.rb
Class Alternative < ActiveRecord::Base
belongs_to :council
end
This will allow you to use the #council object to save any new Alternative objects you need:
#app/controllers/councils_controller.rb
Class CouncilsController < ApplicationController
def show
#council = Council.find params[:id]
#council.alternatives.build
end
def update
#council = Council.find params[:id]
#council.update(council_params)
end
private
def council_params
params.require(:council).permit(alterantives_attributes: [:alternatives, :attributes])
end
end
This will allow you to use the following form code:
#app/views/councils/show.html.erb
<%= form_for #council do |f| %>
<%= f.fields_for :alternatives do |a| %>
<%= a.text_field :your_info %>
<% end %>
<% f.submit %>
<% end %>
What do your routes.rb look like? In this case, it seems like the easiest thing would be to have your alternative nested under your council routes, so something like this:
resources :councils do
resources :alternatives
end
In which case, in your alternatives_controller.rb in the create action, you have to just set the #council object from the parameters.
ie.
def create
#council = Council.find(params[:council_id])
#alternative = Alternative.new(alternative_params)
#alternative.council = #council
#alternative.save
end
Personally I would nest it in the routes as derekyau stated above (https://stackoverflow.com/a/25109545/3105349)
But you could also use a hidden field in the form to pass through the council id i.e.
<%= hidden_field :council_id, #council.id %>
Then in the controller you can access it through the params
#council = Council.find(params[:alternative][:council_id])
or add it to your alternative params to assign it automatically
def alternative_params
params.require(:alternative).permit(:title, :more_info, :council_id)
end
But again, making it a nested route is the cleaner and preferred solution.

How to get Rails build and fields_for to create only a new record and not include existing?

I am using build, fields_for, and accepts_nested_attributes_for to create a new registration note on the same form as a new registration (has many registration notes). Great.
Problem: On the edit form for the existing registration, I want another new registration note to be created, but I don't want to see a field for each of the existing registration notes.
I have this
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
end
and this
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
and in the view I am doing this:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
and it is creating a blank text area for a new registration note (good) and each existing registration note for that registration (no thank you).
Is there a way to only create a new note for that registration and leave the existing ones alone?
EDIT: My previous answer (see below) was bugging me because it's not very nice (it still loops through all the other registration_notes needlessly). After reading the API a bit more, the best way to get the behaviour the OP wanted is to replace:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for :registration_notes, #registration.registration_notes.build do |n| %>
fields_for optionally takes a second parameter which is the specific object to pass to the builder (see the API), which is built inline. It's probably actually better to create and pass the new note in the controller instead of in the form though (just to move the logic out of the view).
Original answer (I was so close):
Just to clarify, you want your edit form to include a new nested registration note (and ignore any other existing ones)? I haven't tested this, but you should be able to do so by replacing:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for #registration.registration_notes.build do |n| %>
EDIT: Okay, from a quick test of my own that doesn't work, but instead you can do:
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content if n.object.id.nil? %>
<% end %>
This will only add the text area if the id of the registration note is nil (ie. it hasn't been saved yet).
Also, I actually tested this first and it does work ;)
If you want to create a new registration form on your edit action, you can just instantiate a new registration_note object. Right now, your form is for the existing registration object.
I believe this is what you want:
class RegistrationsController < ApplicationController
def edit
#new_registration_note = RegistrationNote.new
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
In your view, you should pass a hidden param that references the registration record id:
<%= form_for #new_registration_note do |r| %>
<%= r.hidden_field :registration_id, :value => #registration.id %>
<%= r.text_area :content %>
<% end %>
Now, you can create your new registration note that belongs to #registration. Make sure you have a column in your registration_notes table to point to the registration. You can read more about associations here: http://guides.rubyonrails.org/association_basics.html
Thank you so much for your help as I said in my post the only problem with the approach from "Zaid Crouch"(I don't know how to make a reference to a user hehe) is that if the form has error fields the form will be clear and boom after the page reloading you'll have nothing filled in your form and can you imagine if you form is like 20 or 30 fields that would be a terrible user experience of course
Here is my solution that works with validation models:
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
has_one :new_registration, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration
end
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.build_new_registration
end
end
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
I'm using simple_form in my example if you want to see the same working with validations and transaction take a look at the complete post here:
http://elh.mx/ruby/using-simple_form-for-nested-attributes-models-in-a-has_many-relation-for-only-new-records/
As Heriberto Perez correctly pointed out the solution in the most upvoted answer will simply discard everything if there's a validation error on one of the fields.
My approach is similar to Heriberto's but nevertheless a bit different:
Model:
class Registration < ActiveRecord::Base
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
# Because 0 is never 1 this association will never return any records.
# Above all this association don't return any existing persisted records.
has_many :new_registration_notes, -> { where('0 = 1') }
, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration_notes
end
Controller:
class RegistrationsController < ApplicationController
before_action :set_registration
def edit
#registration.new_registration_notes.build
end
private
def set_registration
#registration = Registration.find(params[:id])
end
def new_registration_params
params.require(:registration).permit(new_registrations_attributes: [:content])
end
end
View:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>

rails - update_attributes for just part of the model; file uploading

I want to add the ability of a user to have several pictures associated with his / her user account.
I have the following classes:
class User < ActiveRecord::Base
has_many :assets
accepts_nested_attributes_for :assets
end
class Asset < ActiveRecord::Base
belongs_to :assetable, :polymorphic => true
belongs_to :user
end
I want to have a screen that just has the upload image functionality:
def add_profile_picture
#user=User.find(params[:id])
1.times {#user.assets.build}
end
form:
<%= form_for #user do |u| %>
<%= u.fields_for :assets do |asset| %>
<%= asset.file_field :asset %>
<%= asset.text_field :description %><br />
<% end %>
<%=u.submit %>
<% end %>
When I submit, it looks like the id value goes in ok in development.log:
"id"=>"1"
but I get the error:
undefined method `update_attributes' for nil:NilClass
Since I just have the asset fields, is there anything special I need to do? Also, because the belongs_to :user exists, could that be causing problems?
Basically:
asset:
user_id:
assetable_type:
assetable_id:
Any help would be appreciated. Don't do much Rails forms stuff.
thx
edit #1
class UsersController < ApplicationController
def add_profile_picture
#user=User.find(params[:id])
1.times {#user.assets.build}
end
thx
Okay - there are a few problems with your code here. I would highly recommend you read both the Action Controller Overview and the Rails Routing guides to get some more information about this.
In any case, you're getting the error because the form you have there will be trying to use the users#update action in the UsersController.
You've got a couple options. One is to create the necessary routes for the custom action, or you can create a nested resource, and make a form for adding the asset.
In this case, you'd do something like this:
in routes.rb
resources :users do
resources :assets, :only => [:new, :create] # Or any other actions you might want. It's best practise to limit these.
end
Then, in the AssetsController, you can do something similar to this:
def new
#asset = Asset.new
end
def create
#asset = Asset.new(params[:asset])
#asset.user_id = params[:user_id] if params[:user_id]
#asset.save!
end
and your form will look something like this:
<%= form_for #asset do |f| %>
<%= f.file_field :asset %>
<%= f.text_field :description %><br />
<%=f.submit %>
<% end %>

Resources