Problems using form helpers in ruby on rails - ruby-on-rails

I have a class activity that has the follow atributes:
String type, Date date, String title
By including the associations it also has user_id and place_id.
class Activity < ActiveRecord::Base
belongs_to :user
belongs_to :place
In the other side User has many activities and place has many activities
So, the problem is when I want to create a new activity:
Scaffold creates the helper _form :
<%= form_for(#activity) do |f| %>
<% if #activity.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#activity.errors.count, "error") %> prohibited this activity from being saved:</h2>
<ul>
<% #activity.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :type %><br>
<%= f.text_field :type %>
</div>
<div class="field">
<%= f.label :date %><br>
<%= f.datetime_select :date %>
</div>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :user_id %><br>
<%= f.number_field :user_id %>
</div>
<div class="field">
<%= f.label :place_id %><br>
<%= f.number_field :place_id %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I want to receive the first 3 fields from the form (type, date and title) but to associate a user and a place I have to do other way. I need the user that is actual logged in and the place is choosen by tiping the name.
My idea to do this is the following:
1) The user issue, I can make a query by using the current_logged_user that I have acess and get his ID.
2) The place issue, I can use the name that I receive from form and query my Places table for the place with the name X and get the ID after.
But, because I don't know too much about rails, how can I do this? How can I use f.text_field and then made the query or whatever and use after in the controller?
Controller has already this stuff :
def create
#activity = Activity.new(activity_params)
(...)
private
# Use callbacks to share common setup or constraints between actions.
def set_activity
#activity = Activity.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def activity_params
params.require(:activity).permit(:type, :date, :title, :user_id, :place_id)
end

You can structure your rails app to get neither the user_id nor the place_id directly from the form. Especially getting user_id from a submitted form is generally not a good idea. You usually do not want to whitelist user_id at all.
For user_id:
If you are using a gem like devise for user authentication, it gives you access to a method called current_user, which you can use to set the user_id from.
For place_id:
I suggest putting the activity as a sub route of place. e.g. instead of having the form under <host>/activities/new, put it under ``/places/:place_id/activities/new`. In your route file put the route as follows:
resources :places do
resources :activities
end
Now, in your controller action you can do the following:
def create
#activity = current_user.activities.new(activity_params)
#activity.place_id = params[:place_id] (or even safer will be #activity.place = Place.find(params[:place_id], but this will require one more sql query )
(...)
private
# Never trust parameters from the scary internet, only allow the white list through.
def activity_params
params.require(:activity).permit(:type, :date, :title)
end
UPDATE:
If you absolutely want to have the form under /activities/new route then you can have a select tag for place_id in your form:
select_tag 'activity[place_id]', options_from_collection_for_select(Place.all, 'id', 'name')
This will create a selection with name 'activity[place_id]' (named this way for params.require(:activity).permit(place_id) ) and options looking like
<option value="1">Barcelona</option>

Related

How to use nested forms in Rails if the fields have the same name?

I have two models, Dog and Owner, and I want their names to be the same, and would be redundant if I asked to fill out the fields twice (once for the dog and another time for the owner). I'm wondering if there's a simpler way to update the two databases with one input.
<h1>Create a new Dog:</h1>
<%= form_for(#dog) do |f|%>
<div>
<%= f.label :name%>
<%= f.text_field :name%>
</div><br>
<div>
<%= f.label :breed%>
<%= f.text_field :breed%>
</div><br>
<div>
<%= f.label :age%>
<%= f.text_field :age%>
</div><br>
<div>
<h3>create a new owner:</h3>
<%= f.fields_for :owner, Owner.new do |owner_attributes|%>
<%= owner_attributes.label :name, "Owner Name:" %>
<%= owner_attributes.text_field :name %>
<% end %>
</div>
<%= f.submit %>
<% end %>
First of all, not sure why you want to keep the name of the owner and the dog same.
However, there can be many ways to achieve what you want:
You can simply omit the owner name from the form.
So you no longer need: <%= owner_attributes.label :name, "Owner Name:" %>
OR you no longer need:
<div>
<%= f.label :name%>
<%= f.text_field :name%>
</div><br>
And in the Owner/Dog model, you can pass the name of the dog/owner in a callback - maybe after_initialize or before_save or before_validation depending on your validation requirements.
class Dog
belongs_to :owner
before_validation :set_name
private
def set_name
self.name = owner&.name
end
end
You can make the owner name as a hidden field instead and can write some javascript to update the hidden field with the dog name before submitting the form or onblur event. I would prefer the first approach since it's simpler and more secure than only JS solution to maintain database consistency
If dogs belongs_to and owner, you don't really need to store the owner's name separately. You can just call dog.owner.name anywhere you have a Dog instance. Having said that, it is relatively straightforward to append attributes on top of the POSTed form values in your controller using .merge():
def create
#dog = Dog.new(dog_params.merge(owner: params[:dog][:owner])[:name])
if #dog.save
...
end
end
def dog_params
params.require(:dog).permit(:name, :breed, :age, owner: %i[name])
end

simple_form with two models?

This is my creation form for Customer model.
While populating customers table I am inserting some data in managers table as well. But I want to add a date picker in this simple_form but that date is only stored in Manger model and Customer model doesn't have date field. How do I do it? What alternative options I have?
new.html.erb
<%= stylesheet_link_tag "customers" %>
<div class="row">
<div class="panel panel-default center" id="new-width">
<div class="panel-body">
<%= simple_form_for #customer do |f| %>
<%= f.input :name,:autocomplete => :off %>
<%= f.input :principalAmount,:autocomplete => :off %>
<%= f.input :interestRate %>
<%= f.input :accountType %>
<%= f.input :duration,:autocomplete => :off %>
<%= f.button :submit %>
<% end %>
</div>
</div>
</div>
Edit: Manager model has many field which is independent of Customer model. But When a customer is created it has to add a date in Manager model which is absent in the Customer model.
I suggest you to use accepts_nested_attributes_for in customers model.
Something like this:
In customer model,
accepts_nested_attributes_for :managers
In view page,inside the existing form
<%= f.fields_for :managers do |m| %>
<%= m.date_field :date %>
<% end %>
You can always add a getter and setter to the customer model and manually set the manager fields. Again it depends on the relationship with manager, if it exists already, etc. but the main point is you can create methods that can then be accessed in the form as customer methods.
# in customer.rb
def manager_date=(date)
manager.date = date
end
def manager_date
manager.date
end
then in the form
<%= f.input :manager_date %>
Note - this is a brief example, you'll need to save the manager somewhere and doing this before or after the customer is updated will depend on your needs.
Another way to do this is to create an attr_accessor for manager_date in customer and if it's there, update the manager after the customer is saved
after_save :update_manager
def update_manager
manager.date = manager_date
manager.save
end

Ruby on Rails: assign relationship on creation

I'm new to Ruby on Rails. There are two models in my project: room and guest. The association is "room has_many guests" and "guest belongs to room".
I have separated views for manage rooms and guests. Rooms don't require "guests" value on creation. However, I want to create new guests and assign it to certain room at the same time. What will be the proper way to do it? How do I transfer the input from web and match the entities in database.
The code is pretty much the same as "Getting Started with Rails". In the tutorial, they add "comments" in the "article" view and use "comment" as a sub-resource of "article". In my case, I treat the two models equally and want to manage them in separated views.
Update:
I used the collection_select and try to work with my guest_controller.
<%= form_for :guest, url: guests_path do |f| %>
<% if #guest.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#guest.errors.count, "error") %> prohibited this guest from being added:
</h2>
<ul>
<% #guest.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :phone %><br>
<%= f.text_field :phone %>
</p>
<p>
<%= f.label :room%><br>
<%= f.text_field :room %>
</p>
<p>
<%= f.label :room %><br>
<%= f.collection_select(:room_id, Room.all, :id, :title) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', guests_path %>
In my guest_controller, the create method called by the form above is :
def create
#guest = Guest.new(guest_params)
#guest.room = Room.find(params[:room_id])
if #guest.save
redirect_to #guest
else
render 'new'
end
end
However, when I create a new guest, it shows that:
ActiveRecord::RecordNotFound in GuestsController#create
Couldn't find Room with 'id'=
I checked that room_id=4 and Room.find(4) return the proper room.
What's wrong?
If you want to select one room from those that exist, use collection_select form helper, here is a relevant snippet from the docs:
f.collection_select(:city_id, City.all, :id, :name)
This outputs a dropdown list that:
fills in city_id parameter in this context
uses City.all for filling in the options in the list (I will be referring to "each" city as city)
uses city.id as data (that gets sent in the form)
shows city.name for each city in the dropdown list (hopefully, human-readable)
Bear in mind though, that in terms of security it's like "look, you can select this, and this and this!", that does not prevent users from selecting an unlisted option: either by modifying form markup by hand or sending handcrafted queries.
So should you ever be limiting access to specific rooms, and list only Room.unlocked (unlocked assumed a scope), make sure the received room_id refers to a room from that scope as well. Most of these problems are dealt with using either validations or careful association management (Room.unlocked.find_by_id(:room_id) that outputs nil if the room is not in that scope).
UPD: as for the latest problem you're having -- your understanding on how the form contents look in params seems to be wrong. It's quite a common misconception actually.
form_for :guest will construct a separate object/hash in params[:guest], with all the form's fields inside it. So it actually is inside params[:guest][:room_id], but no, don't rush with adding the missing part.
You've already built a #guest object from entire params[:guest], so if the room actually exists, it's inside #guest.room already and can be validated inside the model during save. Have a look at Rails validators.
Take a look at the fields_for tag:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for
It allows just that, to create a guest while creating a room and associating each other.

Rails 3 - Create View to Insert Multiple Records

I have what seems like a simple query. I need to create a view that will accept multiple records based on a single model. In my case the model is Project, which has 1 foreign key (person) and 2 fields time, role. I need to create a view (form) to insert 5 roles.
<%= form_for(#project) do |f| %>
<% 5.times do |index|%>
<div class="field">
<%= f.label :position %><br />
<%= f.text_field "fields[#{index}][stime]" %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I get an error message: undefined method `fields[0][stime]'
I do not think the railscasts for nested models is what I need.
How would I go about creating this?
EDIT: The Project model code is below:
class Project < ActiveRecord::Base
belongs_to :person
attr_accessible :role, :stime
end
The Projects_Controller code for the new method is below:
def new
#project = Project.new
end
I see you're planning to make some 1-to-many relationship (Product has_many :roles).
Here's some advices.
First, take a look at the accepts_nested_attributes_for method. You need to add it to your model to be able to perform mass-create.
Second, fields_for is what you need to design nested forms.
I'll give you some example of mass-creating for a simple Product has_many :line_items case:
<%= form_for #product do |f| %>
<%= f.fields_for :line_items, [LineItem.new]*5 do |li_fields| %>
<%= li_fields.text_field :quantity %>
<%= li_fields.text_field :price %>
<br>
<% end %>
<%= f.submit "Create line items" %>
<% end %>
All you need is to write in you controller something like:
#product.update_attributes params[:product]
and 5 line_items will be created at once.
Don't forget to white-list association_attributes (see params in your logs to see it). But I think if you get the mass-assignment error you'll do it anyway :)
I hope it helps.

Rails 3 date field is blank after form submit

Rails newbie here.
I have 2 models: Target and Observation
Target works fine. I generated scaffolding for Observation, like this:
rails generate scaffold Observation date:date target:references
So app/models/observation.rb says:
class Observation < ActiveRecord::Base
belongs_to :target
end
Then I edited app/models/target.rb:
class Target < ActiveRecord::Base
has_many :observations
end
The scaffolding created app/views/observations/_form.html.erb which includes:
<div class="field">
<%= f.label :target %><br />
<%= f.text_field :target %>
</div>
And app/controllers/observation_controller.rb which includes:
def create
#observation = Observation.new(params[:observation])
I then go to create a new Observation. I enter a date and the ID of a target in the target field. When I submit, I get this error in the browser:
ActiveRecord::AssociationTypeMismatch in ObservationsController#create
Target(#2190392620) expected, got String(#2148287480)
Seems like the scaffolding would set up something that would work. But the error makes sense. It's receiving the ID of the Target instead of the Target itself. So I edited app/controllers/observation_controller.rb to say:
def create
#target = Target.find(params[:observation][:target])
#observation = #target.observations.create(:date => params[:observation][:date])
Now it creates the Observation record, with the reference to the Target. But the date field is blank.
I realize this may be a dumb newbie or RTFM question, but I'd really appreciate a pointer in the right direction. Thanks.
Here's the full contents of the form, after changing it to reflect the answer received.
<%= form_for(#observation) do |f| %>
<% if #observation.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#observation.errors.count, "error") %> prohibited this observation from being saved:</h2>
<ul>
<% #observation.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :date %><br />
<%= f.date_select :date %>
</div>
<div class="field">
<%= f.label :target %><br />
<%= f.collection_select :target_id, Target.all, :id, :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
change
<%= f.text_field :target %>
to
<%= f.text_field :target_id %>
And really better is to use something like select for this thing. Like:
<%= f.collection_select :target_id, Target.all, :id, :title %>
UPD
As far as date_select helper set not ordinary banch of variables for each element (year, month, day) you shoul do this:
date = [ params[:observation]['date(1i)'], params[:observation]['date(2i)'], params[:observation]['date(3i)'] ].join(".")
#observation = #target.observations.create(:date => date)
Actually just look into HTML source and you'll see it
(Not sure if you are still monitoring this, OP? For the benefit of everyone coming here via Google:)
There will be no params[:observation][:date] because dates are entered using several HTML input fields, and then magically merged in assignment. The keyword for this is "multi-parameter attributes", and this is the best explanation I've found:
How do ruby on rails multi parameter attributes *really* work (datetime_select)
I also wonder if this simpler snippet would work.
#observation = #target.observations.create(params[:observation])
You can use:
<%= collection_select(:observation, :target_id, Target.all, :id, :title %>
i think it will help you.

Resources