Ruby on Rails: Insert value into database based on user input? - ruby-on-rails

I have an address column, but I'm not using :address in my form, instead, I have
:street, :state, :city, :zip columns:
<%= form_for #user, :html => {:multipart => true} do |f| %>
<%= f.label :street %>
<%= f.text_field :street %>
<%= f.label :state %>
<%= f.text_field :state %>
<%= f.label :city %>
<%= f.text_field :city %>
<%= f.label :zip %>
<%= f.text_field :zip %>
<%= f.submit %>
<% end %>
Because I don't have a field for :address, but I would like to compile the information in the form and still insert it into my database. For example
(12345 Maple St, Made City CA 90017 or maybe when I get more advanced, I'll use some gem to compile the given information and put into a correct address format.)
How do I do such a thing?
My controller looks something like this:
def create
#user = User.new(params[:user])
if #user.save
redirect_to #user, notice: "Successfully created."
else
render :action => 'edit'
end
end

def create
address = [params[:user].delete(:street), params[:user].delete(:state), params[:user].delete(:city), params[:user].delete(:zip)].join(", ")
#user = User.new(params[:user].merge(:address => address))
if #user.save
redirect_to #user, notice: "Successfully created."
else
render :action => 'edit'
end
end

Another option would be to do it in model
attr_accessor :street, :city, :state, :zip
before_create :concate_address_attrs
Edit:-
I feel before_save is better to use over before_create
before_save :concate_address_attrs
def concat_address_attrs
self.address = [street, city, state, zip].join(", ")
end

Unless processing overhead is a major concern, you're breaking Rails' DRY principles by saving concatenated model attributes as a separate attribute.
The Rails Way of accomplishing this would be to create a model convenience method that concatenates existing attributes into a pseudo-address attribute without requiring any additional attributes be committed to the database:
# my_model.rb
def address
[street, city, state, zip].join(', ')
end
# my_view.html.erb
<%= #user.address %>
Alternatively, if you only need the full address for display in your views, you might consider using a view helper:
# app/helpers/my_model_helper.rb
module MyModelHelper
def formatted_address(address)
[address.street, address.city, address.state, address.zip].join(', ')
end
end
# my_view.html.erb
<%= formatted_address(#address) %>

Related

Rails 4: How to pass nested attributes with strong parameters into a model

I've got two nested resources I'm working with here. Stashes & Receivers.
routes.rb
resources :stashes do
resources :receivers
end
I have a form where I'd like to create both a Stash and a Receiver once it's submitted.
<%= form_for(#stash) do |f| %>
<h2>Who is this Stash for?</h2>
<div class="reciever-wrap">
<%= f.fields_for #receiver do |builder| %>
<div><%= builder.label :first_name %><br />
<%= builder.text_field :first_name, :autofocus => true %></div>
<div><%= builder.label :last_name %><br />
<%= builder.text_field :last_name %></div>
<% end %>
<div class="self-check">
<%= check_box_tag(:self) %>
<%= label_tag(:self, "This stash is for me") %></div>
</div>
<div><%= f.label :occasion, "Is this stash for an occassion?" %><br />
<%= f.text_field :occasion %></div>
<div><%= f.label :initial_amount, "How much would you like to spend?" %><br />
<%= f.text_field :initial_amount %></div>
<div><%= f.label :length, "How long would you like this Stash to last?" %><br />
<%= select_tag(:length, options_for_select([['3 Months', 1],['6 Months', 2],['1 Year']])) %> </div>
<div><%= f.submit "Start adding gifts to this Stash!", :class => "button" %></div>
<% end %>
I think that it makes the most sense to create the Receiver first, and then the Stash, so I can associate the Stash with the Receiver. So in the Stashes controller (because the Stash is primary in this form) I have this in the create action:
stashes_controller.rb
def create
receiver = Receiver.create_receiver(current_user, stash_params)
if receiver.id
#stash = Stash.create_stash(current_user, stash_params, receiver)
if #stash.id
flash[:success] = "Stash Started!"
redirect_to stashes_url(#stash), :notice => "Stash Started!"
else
render :action => "new", :notice => "Error, try that again"
end
end
end
I'm also defining the stash_params at the bottom:
def stash_params
params.require(:stash).permit(
:intitial_amount,
:length,
:user_id,
:first_name,
:last_name,
:id,
:receivers_attributes => [:id, :first_name, :last_name])
end
I'm able to create the Stash no problem. But when I go to create the Receiver, I'm running into trouble. The system is creating a new Receiver record, but none of the data from the form is being stored. I'm guessing that it has something to do with not being able to access that nested strong parameter data.
This is the create_receiver method in the Receiver model that should be saving the data, but for some reason isn't. I am able to save the current_user data, so that's good. It's only the data from the strong attributes that isn't being saved:
receiver.rb
def self.create_receiver(current_user, stash_params)
Receiver.create!(
:first_name => stash_params[:first_name],
:last_name => stash_params[:last_name],
:user_id => current_user
)
end
Do I need to be traversing the stash_params to access the nested receiver_params attributes? I'm not sure how I should be accessing the nested attributes in the model. Any help is greatly appreciated!
I also tried moving this create method into the Stash model to see if I could access the attributes from there, but I had no luck with that either.
You're probably missing accepts_nested_attributes_for in your model
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Update #1
Since you've stated that you've included accepts_nested_attributes_for I would suggest consolidating your controller logic to something like
def create
#stash = Stash.new(stash_params)
#stash.user = current_user
#stash.receivers.each { |r| r.user = current_user }
if #stash.save
flash[:success] = "Stash Started!"
redirect_to stashes_url(#stash), :notice => "Stash Started!"
else
render :action => "new", :notice => "Error, try that again"
end
end
end
This way you don't have to check if the individual records were being saved and strong parameters should act properly
Since you're using nested attributes, you should try this in your create action:
def create
stash = Stash.new(stash_params)
if stash.save
flash[:success] = "Stash Started!"
redirect_to stashes_url(#stash), :notice => "Stash Started!"
else
render :action => "new", :notice => "Error, try that again"
end
end
This does all the work needed to save both the stash and receiver. accepts_nested_attribute_for guarantees that the belonged object will be saved and receive the proper id from the parent object when it's being saved while the parent object is also being stored.

Rails 4: Password Validation Error: "{:password=>["can't be blank"]}" even though form is filled out

I'm relatively new to Rails (using Rails 4), and am having a problem with validation for my user model. Even when the form is fully filled in with both the passwords, when I submit the code two errors print out:
{:password=>["can't be blank"], :password_confirmation=>["doesn't match Password"]}
I would like the user to be saved into the database, but these validation errors are preventing that from happening. What I would like to know is what I need to change in order to get rid of these errors.
I am printing out the params object and it looks like this (the authenticity token is omitted here):
params: {"utf8"=>"✓","authenticity_token"=>"[omitted]",
"user"=>{"username"=>"testuser1", "password"=>"test",
"password_confirmation"=>"test", "email_attributes"=>{"email"=>"d#d.com"},
"first_name"=>"test", "last_name"=>"user", "gender"=>"male", "city"=>"la",
"state"=>"ca", "country"=>"usa", "dob"=>"1980-11-20"},
"commit"=>"Create Account", "action"=>"create", "controller"=>"users"}
So it appears that the password and password_confirmation attributes are getting passed correctly. I am wondering if this may have to do with the virtual attribute password I have defined in the user model, but if that is the case I am still not quite sure how to solve this problem. Any help would be greatly appreciated. Let me know if I need to elaborate further.
Here is relevant code for reference:
Controller:
class UsersController < ApplicationController
def new
#user = User.new
#user.build_email
end
def create
if #user = User.create(user_params)
logger.debug "#{#user.errors.messages}"
logger.debug "params: #{params}"
redirect_to :action => "new"
else
logger.debug "#{#user.errors.messages}"
logger.flush
redirect_to :action => "new"
end
end
private
def user_params
params.require(:user).permit(:username, :password, :password_confirmation, :first_name, :last_name, :gender, :dob, :city, :state, :country, :admin_level, email_attributes: [:email])
end
end
Model:
class User < ActiveRecord::Base
has_one :email
validates_presence_of :username, :email, :password
validates_confirmation_of :password, :on => :create
accepts_nested_attributes_for :email
def password_valid?(candidatePass)
candidatePassAndSalt = "#{candidatePass}#{self.salt}"
candidatePasswordDigest = Digest::SHA1.hexdigest(candidatePassAndSalt)
if (candidatePasswordDigest == self.password_digest)
return true
else
return false
end
end
def password
end
def password=(text)
self.salt = Random.new.rand
passAndSalt = "#{text}#{self.salt}"
self.password_digest = Digest::SHA1.hexdigest(passAndSalt)
end
end
View:
<%= form_for #user, url: {action: "create"}, html: {class: "user-creation-form"} do |f| %>
<%= f.text_field :username %>username<br/>
<%= f.password_field :password %>pw<br/>
<%= f.password_field :password_confirmation %>pwcopy<br/>
<%= f.fields_for :email do |email_form| %>
<%= email_form.text_field :email %>email<br />
<% end %>
<%= f.text_field :first_name %>first<br/>
<%= f.text_field :last_name %>last<br/>
<%= f.radio_button :gender, "male" %>
<%= f.label :gender_male, "M" %>
<%= f.radio_button :gender, "female" %>
<%= f.label :gender_female, "F" %><br />
<%= f.text_field :city %>city<br/>
<%= f.text_field :state %>state<br/>
<%= f.text_field :country %>country<br/>
<%= f.date_field :dob %>dob<br/>
<%= f.submit "Create Account" %><br/>
<% end %>
The issue is your empty getter:
def password
end
It always return nil.
2 small additions to the previous answer, which should resolve your issue by the way.
1) If you're using Rails >3 (I assume you are by looking at your user_params method in the controller) you don't have to specify all those password fields and validations.
ActiveRecord automatically includes this ActiveModel method :
has_secure_password
More details at : http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password
2) If the uncrypted password/password_confirmation are shown in your log files your app is insecure. Add this to your config/application.rb :
config.filter_parameters = [:password, :password_confirmation]
This should not be needed if you are using has_secure_password in your User model.

Nested form can't mass assign, create action

I had to add the pen attributes to the paper model to stop the error "can't mass assign :pen", even thought I had the attr_accessible for pen_attributes.
Now, I'm getting a "unknown attribute: pen" error. It's pointing me to the second line of the create action. I can't figure it out.
I basically want to have to have the Paper New action create the pen and assign it to the paper.
Paper model
attr_accessible :name, :size, :line,
:pen_attributes,
:pen, :colour, :style
has_many :pens
accepts_nested_attributes_for :pens
Pens model
attr_accessible :name, :size, :line, :paper_attributes, :paper_id
belongs_to :paper
<%= simple_nested_form_for #paper do |f| %>
<%= f.input :name %>
<%= f.input :size, :placeholder => "text" %>
<%= f.input :line %>
<%= f.fields_for #pen do |h| %>
<%= h.input :pen, %>
<%= h.input :colour %>
<%= h.button :submit, :label => "create" %>
<% end %>
<% end %>
Paper Controller
def new
#user = current_user
#paper = #user.paper.build(params[:paper])
#pen = Pen.new(params[:pen])
end
def create
#user = current_user
#paper = #user.papers.build(params[:paper])
#pen = #paper.pens.build(params[:pen])
if #paper.save
flash[:notice] = "#{#paper.name} Created"
redirect_to(:action => "index")
else
flash.now[:notice] = "Error"
render 'new'
end
end
{"utf8"=>"✓",
"authenticity_token"=>"Z8vncB9ewDM1bWiKfsPHOGlkxcGpfhPjv0xpamudIIs=",
"paper"=>{"name"=>"three",
"size"=>"three",
"colour"=>"red",
"pen"=>{"colour"=>"test",
"pen"=>"test"}},
"commit"=>"Create"}
It looks like you have some minor discrepancies in your singular/plural naming.
I think you need to adjust the following:
<%= f.fields_for :pens, #pen do |h| %>
and probably:
attr_accessible :pens_attributes
as well as (possibly):
params[:pens]
Hope this helps, good luck!

Model not validating

I've tried setting up a form validation that would ensure that at least 1 and at most 3 tags must be included in the form. But it isn't working as an empty form is still processed, but a form with 4 comma-seperated tags is validated correctly.
Controller
def update
#product = Product.find(params[:id])
current_user.tag(#product, :with => params[:product][:tag_list], :on => :tags)
if #product.update_attributes(params[:product])
redirect_to :root, :notice => "Added"
else
render :action => 'edit'
end
end
Form
<%= form_for #product do |f| %>
<%= f.label :tag_list, "Your tags" %> <%= f.text_field :tag_list, :value => #product.tags_from(current_user) %>
<p><%= f.submit "Change" %></p>
<%= f.error_messages %>
<% end %>
Model
validate :required_info
validates_size_of :tag_list,
:maximum => 3
private
def required_info
if( tag_list.empty? and description.empty? )
errors.add_to_base "Add one"
end
end
You could use a custom validation:
validates :tag_list_length
private
def tag_list_length
errors.add(:tag_list, "Must include at least one and no more than three tags") unless tag_list.length.between?(1,3)
end
if( tag_list.empty? and description.empty? )
errors.add_to_base "Add one"
end
Just looking at this part of the model, I think you'd rather do if(tag_list.empty? or description.empty?) because you want both of them to be filled.
For the second validation, I'm not an act_as_taggable user so I can't answer you now.

Cannot get nested form with a has_one association to work

I have these models:
class User < ActiveRecord::Base
has_one :city
accepts_nested_attributes_for :city
end
class City < ActiveRecord::Base
belongs_to :user
end
This controller action:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to(#user, :notice => 'User was successfully created.') }
format.xml { render :xml => #user, :status => :created, :location => #user }
else
format.html { render :action => "new" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
and this view:
<%= form_for :user,:url => users_path,:method => :post do |f| %>
<%= f.fields_for :city do |b| %>
<%= b.collection_select :id,City.all,:id,:name %>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I am trying to allow the user to select a city from the list of already added cities. I am trying to present him a select. The select part it works, but the generated html code for it, looks like this:
<select name="user[city][id]" id="user_city_id">
<option value="1">One</option>
<option value="2">Two</option>
</select>
Notice that it's name doesn't have attribute anywhere. So, when I try to save it, I get this error:
City(#37815120) expected, got ActiveSupport::HashWithIndifferentAccess(#32969916)
How can I fix this?
EDIT: there is some progress, I tried to change the fields_for to this:
<%= f.fields_for :city_attributes do |b| %>
<%= b.collection_select :id,City.all,:id,:name %>
<% end %>
and now, the html seems to generate correctly. But I get this error now:
Couldn't find City with ID=1 for User with ID=
I have no idea what to do next.
EDIT2: overriding the city_attributes= method seems to work:
def city_attributes=(attribs)
self.city = City.find(attribs[:id])
end
I don't know if it's the way to go, but it seems good.
Have a look at this question that seems similar to yours :
Rails 3: How does "accepts_nested_attributes_for" work?
Actually, since the Cities already exsit, I think there is no need for nested forms here.
Try Replacing
<%= f.fields_for :city_attributes do |b| %>
<%= b.collection_select :id,City.all,:id,:name %>
<% end %>
With
<%= f.collection_select :city, City.all,:id,:name %>
Updated afters comments
Could you change your relationship with (and update database scheme accordingly)
class User < ActiveRecord::Base
belongs_to :city
end
class City < ActiveRecord::Base
has_many :users
end
And then try using:
<%= f.collection_select :city_id, City.all,:id,:name %>
You could also do a
<%= f.collection_select :city_id, City.all, :id, :name %>
in your view and then add virtual attributes to your User model:
class User < ActiveRecord::Base
...
def city_id(c_id)
update_attribute(:city, City.find(c_id))
end
def city_id
city.id
end
end
This might not be very clean, since the associated City model is "saved" whenever assigning an ID to some_user.city_id. However, this solution keeps your controller and view nice and clean.
Note: you might also want to account for a blank ID being passed in to the setter method.
Try this
<%= f.select(:city_id, City.all.collect {|p| [ p.name, p.id ] }) %>

Resources