Importing data from a form? - ruby-on-rails

I am trying to import four data fields. Child_id, First_name, Last_name, Medical. In my form it is only pulling in child_id:
<%= form_for #check_in, :url => {:controller => 'check_ins', :action => 'create' } do |f| %>
<% #account.children.each do |child| %>
<%= f.check_box :child_id, {:checked => 'checked', :multiple => true}, child.id.to_s %>
<%= image_tag child.photo.url(:thumb) %>
<span class="name"><%= child.first %>
<%= child.last %></span><br/>
<% end %>
<% end %>
Model associations:
class CheckIn < ActiveRecord::Base
has_many :children
end
class Child < ActiveRecord::Base
belongs_to :account
belongs_to :check_in
end
This is my create method in my check_ins controller.
def create
#check_in = CheckIn.new(params[:check_in])
begin
params[:check_in] [:child_id].each do |child_id|
unless child_id == 0.to_s
CheckIn.new(:child_id => child_id).save!
end
end
respond_to do |format|
format.html { redirect_to(:controller => 'sessions', :action => 'new') }
format.json { render json: #check_in, status: :created, location: #check_in }
end
rescue
respond_to do |format|
format.html { render action: "new" }
format.json { render json: #check_in.errors, status: :unprocessable_entity }
end
end
end
This form is also on a show page. The checkbox is there and next to the checkbox is the information pulled from another table: child.first, child.last. But those fields I want to be selected along with the checkbox like the child_id is.
Right now I have a child saved in my table with an id of 8 it would pull in the 8 but the fields for child.first and child.last aren't pulling into the new table that the id is.

Hm, by "import data field" you mean showing the attributes of child within your form?
The form looks ok to me, now it depends on things outside of this form.
I would check on the following:
Are the fields of child indeed named first, last and photo as used in
the code snippet, and opposed to those you listed in your question?
What are the contents of #account and #account.children? You could output both on your page to check.

I only see one form tag in your form block: f.check_box :child_id. The other stuff like <%=
child.first %> are not part of the form, even though they're within the form block.
EDIT:
There are a number of problems. First, going strictly by the way the associations are set up, CheckIn should not have child_id attribute. It has_many :children while Child belongs_to :check_in. CheckIn should not have a child_id, Child should have a check_in_id. Therefore you should be updating each selected child with a check_in_id value. I'd read up on ActiveRecord associations: http://guides.rubyonrails.org/association_basics.html
Second, the way the form is rendering controls I think you're ending up with multiple checkboxes with the same name. When rails assembles the params hash it's going to ignore all but the last hash item with a particular key. So even if everything else was set up correctly you'd still only save one child to a check in. I'd watch this tutorial on nested attributes:
http://railscasts.com/episodes/196-nested-model-form-part-1?view=asciicast
Lastly, I don't understand what you mean when you say it's not saving child.first and child.last (aka first_name and last_name?). That information is stored in the child object already, correct? Why would that be saved elsewhere?
If all of this was working correctly you'd be able to do things like this:
# find an account
account = Account.find(99)
# create a check_in
check_in = CheckIn.create
# save one or more (or all) of account's children to the check_in
check_in.children << account.children
# see the first name of a child associated with a check_in
check_in.children[0].first

Related

Rails 5 - Acts as Taggable On gem - defined tag lists

I am trying to learn how to use the Acts as Taggable On gem with Rails 5.
I have models called Proposal and Randd::Field. I am trying to tag proposals with tags which are the :title attribute of the Randd::Field table.
My models have:
Proposal
class Proposal < ApplicationRecord
acts_as_taggable_on :randd_maturities, :randd_fields, :randd_purposes, :randd_activities
# acts_as_taggable
# acts_as_taggable_on :skills, :interests
Randd::Field
(no association on Proposal).
Proposal helper
module ProposalsHelper
include ActsAsTaggableOn::TagsHelper
In my proposal form, I try to add tags:
<%#= f.select :tag_list %>
<%#= f.input :randd_field_list, collection: #randd_fields, label_method: :title, include_blank: false %>
<%= f.text_field :randd_field_list, input_html: {value: f.object.randd_field_list.to_s} %>
In my proposal controller, I have whitelisted an array of randd_field_list (which should hold each of the tags entered via the form).
def proposal_params
params.require(:proposal).permit(:title, :randd_maturity_list, :randd_fields_list,
I can add tags via the console. I cannot get this to work in the proposal form itself. In the console I can do:
p = Proposal.first
p.randd_field_list = [Randd::Field.last.title, Randd::Field.first.title]
p.save
This works to add the title of the first and last Randd::Fields to the array of tags on the proposal.
However, I can't figure out how to achieve this in the form. I get no errors showing in the rails s console. I cant see how to figure this out.
The Acts as Taggable On gem documentation this tutorial for editing tags - it suggests adding an update method to the Randd::Fields controller so that the tag can be updated. Taking that advice, I've tried to add the similar actions to my Randd::FieldsController as:
def edit
end
def update
#randd_field_list = ActsAsTaggableOn::Randd::Field.find(params[:id])
respond_to do |format|
if #randd_field_list.update(randd_field_list_params)
format.html { redirect_to root_path, notice: 'Tag was successfully updated.' }
format.json { render :show, status: :ok, location: #randd_field_list.proposal }
else
format.html { render :edit }
format.json { render json: #tag.errors, status: :unprocessable_entity }
end
end
This does nothing. I'm not sure if its a problem that I don't have a Tags Controller (at all), or if this is the generic label used for all controllers that are the tagging object. Is there anything required in the Proposal controller itself to handle the creation and updating of tags (which for my case are the titles of instances in the Randd::Field model?
Can anyone see what I need to do in order to use the tagging functionality provided by this gem? If I can do it in the console, it follows that I should be able to do it in the code - but its entirely unclear to me as to how to go about implementing this.
def proposal_params
params.require(:proposal).permit(:title, randd_maturity_list: [], randd_field_list:[]
end
You need to permit list params as an array, and make the tag list field on form to pass an array instead of text

How to save a nested resource in ActiveRecord using a single form (Ruby on Rails 5)

I have two entities with a many-to-one relationship. User has many Addresses. When creating a User I want the form to also create a single Address. The entities are nested.
Approach 1:
The code below works, but only saves the User, no associated Address.
Reading around, I thought that the accepts_nested_attributes_for would automatically save the address. I'm not sure, but it may be that this isn't working because the parameters I'm getting into the Controller don't actually appear to be nested, ie. they look like:
"user"=>{"name"=>"test"}, "address"=>{"address"=>"test"}
Rather than being nested like this:
"user"=>{"name"=>"test", "address"=>{"address"=>"test"} }
I assume this could be due to something wrong in my form, but I don't know what the problem is...
Approach 2:
I have also tried changing the controller - implementing a second private method, address_params, which looked like params.require(:address).permit(:address), and then explicitly creating the address with #user.address.build(address_params) in the create method.
When tracing through this approach with a debugger the Address entity did indeed get created successfully, however the respond_to do raised an ArgumentError for reasons I don't understand ("respond_to takes either types or a block, never both"), and this rolls everything back before hitting the save method...
[EDIT] - The respond_to do raising an error was a red herring - I was misinterpreting the debugger. However, the transaction is rolled back for reasons I don't understand.
Questions:
Is one or the other approach more standard for Rails? (or maybe neither are and I'm fundamentally misunderstanding something)
What am I doing wrong in either / both of these approaches, and how to fix them so both User and Address are saved?
Relevant code below (which implements Approach 1 above, and generates the non-nested params as noted):
user.rb
class User < ApplicationRecord
has_many :address
accepts_nested_attributes_for :address
end
address.rb
class Address < ApplicationRecord
belongs_to :user
end
users_controller.rb
class UsersController < ApplicationController
# GET /users/new
def new
#user = User.new
end
# POST /users
# POST /users.json
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user}
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
private
def user_params
params.require(:user).permit(:name, address_attributes: [:address])
end
end
_form.html.erb
<%= form_for(user) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<%= fields_for(user.address.build) do |u| %>
<div class="field">
<%= u.label :address %>
<%= u.text_field :address %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
UPDATE 1:
After making the changes suggested by #Ren, I can see that the parameters look more like what I would've expected for nested resources:
"user"=>{"name"=>"test", "addresses_attributes"=>{"0"=>{"address"=>"test"}}}
However, when trying to save the user, the transaction is still rolled back for reasons I don't understand. The output I get from the users.new page is:
2 error prohibited this user from being saved:
Addresses user must exist
Addresses user can't be blank
However, using byebug, after the #user = User.new(user_params) call, things look as I would expect them:
(byebug) #user
#<User id: nil, name: "test", created_at: nil, updated_at: nil>
(byebug) #user.addresses
#<ActiveRecord::Associations::CollectionProxy [#<Address id: nil, user_id: nil, address: "test", created_at: nil, updated_at: nil>]>
Obviously the user.id field is not set until the record is written to the DB, so equally the address.user_id field cannot be set until user is saved, so maybe this is caused by some sort of incorrect ordering when ActiveRecord is saving to the database? I will continue to try to understand what's going on by debugging with byebug...
UPDATE 2:
Using rails console to test, saving User first and then adding the Address works (both records get written to the DB, although obviously in 2 separate transactions):
> user = User.new(name: "consoleTest")
> user.save
> user.addresses.build(address: "consoleTest")
> user.save
Saving only once at the end results in the same issues I'm seeing when running my program, ie. the transaction is rolled back for some reason:
> user = User.new(name: "consoleTest")
> user.addresses.build(address: "consoleTest")
> user.save
As far as I can tell from debugging with rails console, the only difference between the state of user.addresses in these two approaches is that in the first address.user_id is already set, since the user.id is already known, while as in the second, it is not. So this may be the problem, but from what I understand, the save method should ensure entities are saved in the correct order such that this is not a problem. Ideally it would be nice to be able to see which entities save is trying to write to the DB and in which order, but debugging this with byebug takes me down an ActiveRecord rabbit-hold I don't understand at all!
UPDATE: As opposed to previous versions, Rails 5 now makes it required that in a parent-child belongs_to relationship, the associated id of the parent must be present by default upon saving the child. Otherwise, there will be a validation error. And apparently it isn't allowing you to save the parent and child all in one step... So for the below solution to work, a fix would be to add optional: true to the belongs_to association in the Address model:
class Address < ApplicationRecord
belongs_to :user, optional: true
end
See my answer in a question that branched off from this one:
https://stackoverflow.com/a/39688720/5531936
It seems to me that you are mixing up the singular and plural of your address object in such a way that is not in accordance with Rails. If a User has many addresses, then your Model should show has_many :addresses and accepts_nested_attributes_for should have addresses:
class User < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses
end
and your strong params in your controller should have addresses_attributes:
def user_params
params.require(:user).permit(:name, addresses_attributes: [:id, :address])
end
Now if you want the User to just save One Address, then in your form you should have available just one instance of a nested address:
def new
#user = User.new
#user.addresses.build
end
By the way it seems like your form has fields_for when it should be f.fields_for:
<%= f.fields_for :addresses do |u| %>
<div class="field">
<%= u.label :address %>
<%= u.text_field :address %>
</div>
<% end %>
I highly recommend that you take a look at the Rails guide documentation on Nested Forms, section 9.2. It has a similar example where a Person has_many Addresses. To quote that source:
When an association accepts nested attributes fields_for renders its
block once for every element of the association. In particular, if a
person has no addresses it renders nothing. A common pattern is for
the controller to build one or more empty children so that at least
one set of fields is shown to the user. The example below would result
in 2 sets of address fields being rendered on the new person form.
def new
#person = Person.new
2.times { #person.addresses.build}
end

rails different form attributes based on category

I have a rails app!
I'd like to create a form for a product model, where users can choose a product category first and then can fill the form out.
This would be easy, but I'd like to show them different attributes based on the chosen category. Something like if they choose book category, then they will have fields like title, author, published_at, but if they choose shoes category then they can fill out the size, color and type fields.
I saw afew tuts about dynamic forms, but as far as I understand it, I don't need that since the form fields will be predefined and users won't be able to add extra fields.
What is the good approach in this case? Should I create more different models like (shoes,books, etc.) or something else?
Should I create more different models
No, I don't think that's necessary.
What you'd be best doing is using ajax to populate the form on category change. This would require some configuration, but will make it the most efficient and secure:
#config/routes.rb
resources :products do
put :new, on: :new #-> url.com/products/new
end
#app/controllers/products_controller.rb
class ProductsController < ApplicationController
def new
if request.get?
#product = Product.new
#categories = Category.all
elsif request.put?
#category = params[:product][:category_id]
#attributes = ...
end
respond_to do |format|
format.js
format.html
end
end
end
#app/views/products/new.html.erb
<%= form_for #product do |f| %>
<%= f.collection_select :category_id, #categories, :id, :name, {}, { data: { remote: true, url: new_product_path, method: :put }} %>
<div class="attributes"></div>
<%= f.submit %>
<% end %>
#app/views/products/new.js.erb
$attributes = $(...); // need a way to create form elements from #attributes
$("form#new_product .attributes").html( $attributes );
Something important to note is that Rails select & check elements allow you to use the data-remote attribute to send an ajax call to your controller on change.
Not much documentation about it, playing around with the above code should get it to work.

rails check complete date_select and time_select param

I have an update form where I want a user to update a date. The date should always be NEWER than the date which is currently in the database so I want my script to validate it after the user hits the submit button.
I've come this far:
<%= simple_form_for(calendar, :html => { :method => :put, :name => 'extend_link' }) do |f| %>
<p>Select the new date (must be newer than the current date): <%= f.date_select :end_at %> at <%= f.time_select :end_at, { :ignore_date => true } %></p>
<% end %>
standard update put in my controller, updating the calendar model
def update
#calendar = current_user.calendar.find(params[:id])
respond_to do |format|
if #calendar.update_attributes(params[:calendar])
format.html { redirect_to calendar_path, notice: 'The end date was extended.' }
format.json { head :no_content }
end
end
end
I've checked the source after the form was rendered to understand how the date and time select works and also after researching a lot it is clear that the date is being split into different pieces before it is "merged" into my model and the end_at column
calendar[end_at(3i)]
calendar[end_at(2i)]
....
but for some reason, I cannot access the complete params[:end_at] after the form was submitted. However, it mus be accessible as otherwise how could the model get updated in one piece? I went nuts with this.
It could be so easy:
if params[:end_at] < #calendar.end_at
puts "The new ending date is not after the current ending date."
else
#calendar.update_attributes(params[:calendar])
end
why does it not work and how I can solve my problem?
Thanks for any help.
You can do this in your controller, but it sounds like this is a model validation, so I'd put it there. Use the magic of ActiveModel::Dirty to find the attribute before and after, perhaps something like this:
class Calendar < ActiveRecord::base
validate :date_moved_to_future
private
def date_moved_to_future
self.errors.add(:end_at, "must be after the current end at") if self.end_at_changed? && self.end_at_was < self.end_at
end
end

Rails 3: Single Model backing multiple Controllers

Ok, I have search Google, API's as well as StackOverflow and have found no real decisive help for my issue. So here goes!
I have a Polymorphic model setup named Favorite and it ties to the User. Being that Favorite is Polymorphic I of course can use the relationship to allow my user to add pretty much any entity in my application as their Favorite.
Each of these Favorite relationships between the user and a specific model I want to be able to call different things such as 'Favorite' or 'Like' or 'Friends'. This allows me to have a different Controller with Views to manage each of these different relationships so they are more understandable to the user and myself. Hence I am covering the global generic idea of Favorites with a more precise idea of a 'Friend'.
So I went ahead and created a Friend controller with its associated views to handle the Favorite relationship between a user and other user's in the system.
But what I have found is that Rails expects me to pass a 'Friend' model in all of my interactions between views and controller even though I want to use the Favorite model and I get 'uninitialized constant Friend' as an error in my view. How do I get past this 'convention', how do I make the controller and views if necessary understand that I am using the Favorite model as my underlying model not the Friend?
I considered creating a new model named 'Friend' and inheriting it from 'Favorite' just to fool the controller, but man that just seems like a waste of energy to me. Any ideas out there?
CODE EXAMPLE this is using the Favorite polymorphic model to ButtSlap another User. Each form partial is pass the User as a local variable called local_entity.
ButtSlapController
class ButtSlapsController < AuthorizedResourceController
def create
#favorite = current_user.favorites.build(params[:favorite])
respond_to do |format|
if #favorite.save
flash[:success] = 'butt slap successful!'
format.html { redirect_to('/lounge') }
# format.js { render :action => "create_success"}
else
flash[:success] = 'ah poop!'
format.html { redirect_to('/lounge') }
# format.js { render :action => "create_failure"}
end
end
end
def destroy
#favorite = current_user.favorites.find(params[:id])
respond_to do |format|
if #favorite.destroy
flash[:success] = 'butt slap has been successfully removed.'
format.html { redirect_to('/lounge') }
# format.js { render :action => "create_success"}
else
flash[:success] = 'ah poop!'
format.html { redirect_to('/lounge') }
# format.js { render :action => "create_failure"}
end
end
end
end
Creates The ButtSlap
<%= form_for current_user.favorites.build, :as => :favorite, :url => butt_slaps_path do |f| %>
<div><%= f.hidden_field :favorable_id, :value => local_entity.id %></div>
<div><%= f.hidden_field :favorable_type, :value => local_entity.class.to_s %></div>
<div class="actions"><%= f.submit "butt slap!" %></div>
<% end %>
Removes the ButtSlap
<%= form_for current_user.get_favorites(
{:id => local_entity.id,
:type => local_entity.class.to_s}),
:html => { :method => :delete }, :url => butt_slaps_path do |f| %>
<div class="actions"><%= f.submit "take back" %></div>
<% end %>
Well it all turned out to be a little gem called CanCan v1.6.4
I have been using CanCan for Authorization within my application and when declaring your authorization rules in your Ability class you can either do it by Model or by Controller or a mixture.
In order to handle this I setup 2 root Controllers which inherited from ApplicationController. The first 'AuthorizedController' is used for all controllers which do not use a Model and the second 'AuthorizedResourceController' is used for all controllers which are backed by a Model.
Turns out that for my ButtSlap controller I had it setup as an AuthorizedResourceController and by doing so CanCan was automatically looking to pull and authorize either a collection or a single model based off of the controller's name 'ButtSlap'. But due to the fact that I was using the Favorite model on the backend every time I tried to post to the controller CanCan tried to load its imaginary model based off of its convention. And I thus received the errors messages 'Uninitialized Constant 'ModelName''.
Once I switched the ButtSlapController from an AuthorizedResourceController over to a AuthorizedController CanCan no longer looked to instantiate and authorize a model based off the controller name and it moved to controller based authorization instead and just like everyone was saying 'Poof' my confusion as to why Rails was looking for a Model tied to a controller name was gone.
You really have to love bugs like this, they really stretch your limits as well as your keyboard stockpile (I tend to throw keyboards when I get frustrated ;)

Resources