I don't think my helper method is being accessed when trying to saving to my database. A new instance of Airport is being created but the data I'm expecting from the API is not there. It should bring in the airport's name based on its IATA code the user entered in a form_for in the view.
In other words, "name" is always nil in my db. Therefore doesn't seem like the API is being tapped at all, and name is never sent to the controller to save, which leads me to believe there is no call to the helper for some reason.
If it is actually being called, why is "name" not being filled?
This is my controller:
class AirportsController < ApplicationController
include AirportsHelper
def new
#airport = Airport.new
end
def create
new_airport = Airport.create(params[:airport])
if new_airport.errors.empty?
create_location(params[:airport][:code]) #should call the create_location method in AirportsHelper
redirect_to airport_path(new_airport.id)
else
flash[:notice] = new_airport.errors.full_messages
redirect_to new_airport_path
end
end
def show
#airport = Airport.find(params[:id])
end
end
And here is my helper file:
module AirportsHelper
def create_location(airport_code)
airport = Airport.find_by_code(airport_code) #looks up db based on arpt code
result = Typhoeus.get("https://api.flightstats.com/flex/airports/rest/v1/json/iata/#{airport}?appId=[APP ID]&appKey=[APP KEY]")
result_hash = JSON.parse(result.body)
result_hash['airports'].each do |airport|
#airport_name = airport['name']
end
Airport.update_attributes(name: #airport_name, airport_id: airport.id)
Location.create(name: #airport_name, airport_id: airport.id)
airport.update_attributes(name: #airport_name)
airport.save
end
end
This is my form (built in a partial):
<%= form_for #airport do |f| %>
<%= f.text_field :city, :placeholder => "City" %> <p>
<%= f.text_field :country, :placeholder => "Country" %> <p>
<%= f.text_field :code, :placeholder => "3-letter code" %> <p>
<%= f.text_area :details, :placeholder => "Airport details" %> <p>
<%= f.submit %>
<% end %>
The model has the correct attributes:
class Airport < ActiveRecord::Base
attr_accessible :city, :code, :country, :details, :name
end
I've heard it isn't good practice to call a helper in a controller but I don't know where to put it so that it's called at the right time.
I'm still getting up to speed with Rails so any debugging help would be appreciated!
You've got a typo in your create_location method, with 'aiports' instead of 'airports'.
Figured it out!
Turns out the helper method was working just fine. So anyone looking for problems with their helper modules, this may be a good reference on what a working one looks like.
The problem was with the JSON call as #PeterAlfvin suggested. It was not taking the correct data.
Here's the correct helper method:
module AirportsHelper
def create_location(airport_code)
airport = Airport.find_by_code(airport_code)
result = Typhoeus.get("https://api.flightstats.com/flex/airports/rest/v1/json/iata/#{airport_code}?appId=[APP ID]&appKey=[APP KEY]")
result_hash = JSON.parse(result.body)
result_hash['airports'].each do |airport|
#airport_name = airport['name']
end
airport.update_attributes(name: #airport_name)
airport.save
end
end
Note the string interpolation change in the API get request.
Related
I'm working on a form object that involves updating a User record with it's many associated models like Address and Phone via nested attributes.
Both Address and Phone models have a number attribute, so I name each HTML input and form object attribute differently for each one.
<%= simple_form_for #user_form, method: :patch do |f| %>
<%= f.input :number %>
<%= f.input :phone %>
<% end %>
controller:
def edit
#user_form = UserForm.new(#user)
end
def update
#user_form = UserForm.new(#user, update_params)
if #customer_update.save
redirect_to :users_path
else
render :edit
end
end
def update_params
params.require(:user_form).permit(:number, :phone)
end
Simplified form object:
class UserForm
include ActiveModel::Model
include ActiveModel::Validations::Callbacks
attr_accessor :number
:phone
attr_reader :user
validate :validate_children
def initialize(user, update_params = nil)
#user = user
super(update_params)
end
def save
user.assign_attributes(user_params)
return false if invalid?
user.save
end
def user_params
{}.tap do |p|
p[:address_attributes] = {number: number}
p[:phone_attributes] = {number: phone}
end.compact
end
def validate_children
promote_errors(user.associated_model.errors) if associated_model.invalid?
end
def promote_errors(child_errors)
child_errors.each do |attribute, message|
errors.add(attribute, message)
end
end
end
I delegate validation to the actual Address and Phone models where there is a
validates :number, presence: true
so if validations on either models kick in, the errors are promoted up to the form object and displayed on the invalid input field.
#user_form.errors.keys
=> [:"phone.number", :number]
#user_form.errors.full_messages
=> ["Number can't be blank"]
The problem is that if for example the :phone field is left blank when the form is submited, because the actual attribute that is invalid is the Phone number, the error is displayed in the :number input field in my form, or in other words the Phone number error is rendered in the Address number field because the form input is called the same (f.input :number).
Is there a way to change that or promote the errors differently so this doesn't happen? The code above is obviously dumbed down, let me know if specifics are needed.
That seems like a lot of unnecessary complexity.
The way you would typically set this up in Rails is to use fields_for (and the SF wrapper simple_fields_for):
<%= form_with(model: #user) do |form| %>
<%= form.fields_for(:phone) do |phone_fields| %>
<pre><%= phone_fields.object.errors.full_messages.inspect %></pre>
<% end %>
<%= form.fields_for(:number) do |number_fields| %>
<pre><%= number_fields.object.errors.full_messages.inspect %></pre>
<% end %>
<% end %>
These methods yield a form builder and calling object gets the model instance wrapped by the form builder. This makes it easy to either display the errors together or per attribute and get it right - this is especially true if you're dealing with a one to many or many to many instead of one to one.
I have created a controller called insert. Which is defined like this
class InsertController < ApplicationController
def create
end
def show
render plain: params[:insert].inspect
end
end
My create.html.erb file is defined like this
<%= form_for :insert, url: '/insert/show' do |f| %>
<%= f.text_field :fname, placeholder: "Enter First Name" %><br />
<%= f.text_field :lname, placeholder: "Enter Last Name" %><br />
<%= f.submit "Login"%>
<% end %>
routes.rb is as follows
Rails.application.routes.draw do
get '' => 'greeter#hello'
get 'new' => 'insert#create'
post 'insert/show' => 'insert#show'
end
When I am entering the form below
I am having this output
Till now no issues. But I want to render the output like this
What is the way to access those hash key-value pair?
I have seen examples where they are first storing these data to database and then from database they are fetching these. Is it possible to show these value like my intended output?
You can create show.html.erb file
<%= debug(params) %>
And change your show action to just empty method
def show
end
debug method will display params hash with more human readable way.
I'm still learning more about Rails and I'm starting to play around with APIs, but I can't seem to figure out how to get an input from a form to the Model.
I want to take the User input (in the form of a zip code) and have it spit out the weather info at that user location.
Form on home.html.erb
<%= form_tag(root_path) do %>
<%= label_tag :zip, "ENTER YOUR ZIPCODE TO FIND YOUR WEATHER" %><br>
<%= text_field_tag :zip,'', placeholder: "e.g. 91765 " %>
<%= submit_tag "show me the weather!" %>
<% end %>
Controller pages_controller.rb
class PagesController < ApplicationController
def home
#weather_lookup = WeatherLookup.new(params[:zip])
end
end
Model weather_lookup.rb
class WeatherLookup
attr_accessor :temperature, :weather_condition, :city, :state, :zip
def initialize(zip)
self.zip = zip
zip = 91765 if zip.blank?
weather_hash = fetch_weather(zip)
weather_values(weather_hash)
end
def fetch_weather(zip)
p zip
HTTParty.get("http://api.wunderground.com/api/API-KEY-HERE/geolookup/conditions/q/#{zip}.json")
end
def weather_values(weather_hash)
self.temperature = weather_hash.parsed_response['current_observation']['temp_f']
self.weather_condition = weather_hash.parsed_response['current_observation']['weather']
self.city = weather_hash.parsed_response['location']['city']
self.state = weather_hash.parsed_response['location']['state']
end
end
I'm not exactly sure how to get the input from the form to the model. This is literally just to show the weather. I'm not trying to save anything in a database
The form helper defaults to "POST" if you don't provide a method. From the looks of your controller, "GET" is what you want. Here's some documentation to provide additional context. The updated form:
<%= form_tag(root_path, method: "get") do %>
<%= label_tag :zip, "ENTER YOUR ZIPCODE TO FIND YOUR WEATHER" %><br>
<%= text_field_tag :zip,'', placeholder: "e.g. 91765 " %>
<%= submit_tag "show me the weather!" %>
<% end %>
Next, if you try to instantiate your #weather_lookup variable without params[:zip], Rails will throw an error. Adding a conditional to your controller will solve this:
class PagesController < ApplicationController
def home
if params[:zip]
#weather_lookup = WeatherLookup.new(params[:zip])
end
end
end
Be sure your routes are set up. Something defining root should exist in routes.rb. For example:
root "pages#home"
I believe you also have to parse the JSON into a hash inside your model. Adding that to the weather_values method:
def weather_values(weather_json)
weather_hash = JSON.parse weather_json
self.temperature = weather_hash.parsed_response['current_observation']['temp_f']
self.weather_condition = weather_hash.parsed_response['current_observation']['weather']
self.city = weather_hash.parsed_response['location']['city']
self.state = weather_hash.parsed_response['location']['state']
end
Finally, be sure you're referencing #weather_lookup somewhere in your view, else the data won't show. A simple, unformatted example:
<%= #weather_lookup %>
Assuming the logic works in your model, the JSON should render after you submit a ZIP code via the form. I don't have an API key else I would have tested this myself.
It seems like you are not hitting your home controller after you hit submit. Make sure you are routing correctly with
root to: 'pages#home'
and add this to your form
<%= form_tag(root_path, method: 'get') do %>
I want to be able to access :to_whom text value via params[:to_whom] in the controller. To_whom does not exist in a model.
I get the sensible error:
'undefined method `to_whom' for Conversation'
How can I add an arbitrary attribute to pass back to the controller in rails?
Also, in my view I did Message.new and Conversation.new which is incredibly ugly. I initially set #conversation = Conversation.new in the controller, however I found I had to recreate those variables in the second controller method anyways, which makes sense (after I hit the submit button). Thus instead of setting #message, #conversation in the new method, I removed all the lines from new and did the .new syntax in the view. Is there a more elegant way of writing this code so it isn't so hacky feeling?
CONTROLLER:
class ConversationsController < ApplicationController
attr_accessor :conversation, :user, :to_whom
# this is the method that generates the below view
def new
end
def create
...
end
end
VIEW:
<%= form_for([current_user, Conversation.new]) do |c| %>
<%= c.label :to_whom %>
<%= c.text_field :to_whom %>
<%= c.label :subject %>
<%= c.text_field :subject %>
<%= form_for(Message.new) do |m| %>
<%= m.label :message %>
<%= m.text_field :text %>
<div class="actions">
<%= submit_tag "send" %>
</div>
<% end %>
<% end %>
Virtual Attributes
Your attr_accessor belongs in your model (not your controller). Currently, you have it stored in your controller, which will do nothing at the model-level:
#app/models/conversation.rb
Class Conversation < ActiveRecord::Base
attr_accessor :conversation, :user, :to_whom
end
#app/controllers/conversations_controller.rb
# remove attr_accessor
You have to remember that since Ruby is object-orientated, all the data objects you get are from the model. This means if you call #conversation = Conversation.new, the attributes of that model are actually created in the conversation model
Normally, the model will set the attributes in accordance with your database columns. If you don't have a database column present, you need to create the relevant getter / setter methods using the attr_accessor module
m.text_field :to_whom is just a helper to build an html input tag. You could write your own in raw html, filling in the blanks with erb tags, or you could use other helpers, such as text_field_tag:
text_field_tag "to_whom", params[:to_whom]
I think you need to use fields_for helper, It should be something like below,
= c.fields_for :message do
I'm using Rails 2.3.2, and trying to get a nested object form to work properly. I've narrowed my problem to the issue that Rails is not setting my nested form elements with the *_attributes required to initiate the accepts_nested_attributes_for processing.
My model code is:
class Person < Party
has_one :name, :class_name => "PersonName"
accepts_nested_attributes_for :name, :allow_destroy => true
end
class PersonName < ActiveRecord::Base
belongs_to :person
end
My view code looks like this (I'm using HAML):
%h3 New customer
= error_messages_for :person, :person_name, :name, :country
- form_for :person, :url => collection_url, :html => {:class => 'MainForm'} do |person_form|
- #person.build_name unless #person.name
- person_form.fields_for :name do |name_form|
= name_form.label :given_name, "First Name:"
= name_form.text_field :given_name
= name_form.label :family_name, "Last Name:"
= name_form.text_field :family_name
= hidden_field_tag :inviter_id, params[:inviter_id]
= hidden_field_tag :inviter_code, params[:inviter_code]
%p= submit_tag "Create"
= link_to 'Back', collection_url
Instead of params being:
{"person"=>{"name_attributes"=>{"given_name"=>"Fred", "family_name"=>"Flintstone"}}, ...}
I get:
{"person"=>{"name"=>{"given_name"=>"Fred", "family_name"=>"Flintstone"}}, ...}
As a result, I get a TypeMismatch exception. I've followed the documentation from Ryan Daigle. I've also followed the advice from this blog and the complex-forms-example.
Using Firebug, I went through my form and adjusted the name attribute of the input tags from name to name_attributes. This produced the params with name_attributes, and the create worked fine.
I'm stuck as I cannot figure out why my form is not producing the *_attributes form of the name.
Another thing I tried is I got the complex_form_example working in my environment. I've gone through every inch of the controller, models and views and compared it to my code. I cannot find what is different. I know this is something small, and would appreciate any help!
Thanks!
Post backs do not get routed to the right place
def new
#person = Person.new
end
<% form_for #person do |f| %>
<% f.fields_for :name_attributes do |p| %>
...
<% end %>
<% end %>
Post backs get routed to the right place
def new
#person = Person.new
#person.name = PersonName.new # << this allows fields_for :relation vs :relation_attributes
end
<% form_for #person do |f| %>
<% f.fields_for :name do |p| %>
...
<% end %>
<% end %>
No need to #person.name again in #create
Try to use an actual object for form_for:
form_for :person => form_for #person
I have just been struggling for about an hour with exactly the same problem!
Follow nowk's pattern for the new method in the controller, then put this in your view
<% form.fields_for :name, #person.name do |name_form| %>
<% end %>
Good luck if you try it, that's what worked for me.
Not sure why this isn't working for, but as a workaround, you could just use params[:name] in your controller#create method to update the person record.
person = Person.new(params[:person])
person.name << PersonName.new(params[:name])
Unfortunately, I still have not been able to figure out why this form wouldn't work with nested object forms. I stripped it down to the simplest data, started over using the complex-form-example as a start. I ended using the active_presenter to gather x-object data from the form. I'll revisit nested object forms sometime in the form. Thanks for your help.
Thought I would share my solution as it's slightly different - in my case I want the nested attributes to be dynamic.
In new action:
case params[:type]
when "clubber"
#account = resource.send "build_#{params[:type]}"
when "promoter"
#account = resource.send "build_#{params[:type]}"
when "company"
#account = resource.send "build_#{params[:type]}"
when "venue_owner"
flash[:notice] = 'We ask that venue owners register via the web. Thanks.'
redirect_to root_path and return
end
In my view:
= f.fields_for #account.class.name.downcase+'_attributes' do |form|
Pretty ghetto, but it works.