Rails 4 - Country Select & Simple Form - ruby-on-rails

I am trying to make an app in Rails 4. I use simple form for forms and country_select gem for country lists.
I have an address model, which includes this method:
def country_name
self.country = ISO3166::Country[country]
country.translations[I18n.locale.to_s] || country.name
end
When I try to save a new address, I get this error:
undefined method `translations' for "Australia":String
Can anyone see what's wrong with this method definition?
If I change my view to:
<% if #profile.addresses.any? %>
<%= #profile.addresses.first.country.titlecase %>
<% else %>
<span class="profileeditlink">
<%= link_to "Add your location", new_address_path %>
</span>
<% end %>
Then the record displays - but as AU instead of Australia (which is what the method in the address model provides).
Address table:
create_table "addresses", force: :cascade do |t|
t.string "unit"
t.string "building"
t.string "street_number"
t.string "street"
t.string "city"
t.string "region"
t.string "zip"
t.string "country"
t.boolean "main_address"
t.boolean "project_offsite"
t.string "time_zone"
t.float "latitude"
t.float "longitude"
t.integer "addressable_id"
t.integer "addressable_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "addresses", ["addressable_type", "addressable_id"], name: "index_addresses_on_addressable_type_and_addressable_id", unique: true, using: :btree
TAKING JAEHYEN'S SUGGESTION,
I changed my country name method in the address model to:
def country_name
# self.country = ISO3166::Country(country)
# country.translations[I18n.locale.to_s] || country.name
iso_country = ISO3166::Country.find_by_name[country] # `country` should be name like 'Australia'
iso_country.translations[I18n.locale.to_s] || iso_country.name
end
I get this error:
undefined method `translations' for nil:NilClass
ANOTHER ATTEMPT:
I found this resource: http://www.scriptscoop.net/t/4ee6d5ef4577/displaying-countries-using-country-select-gem-in-rails-4.html
I tried changing my form input to:
<%= f.country_select :country, priority: [ "Australia", "New Zealand", "United Kingdom" ] %>
It still just displays the country code instead of the country name. I'm stuck.
ANOTHER ATTEMPT
I found this post:
Rails Simple_Form: How to show long name of the country
The answer in this post suggests defining country_name as:
def country_name
country = ISO3166::Country[country_code]
country.translations[I18n.locale.to_s] || country.name
end
This is slightly different to my previous attempts, however, when I try this, I get this error:
undefined local variable or method `country_code' for #<Address:0x007fbae5bfb290>
I tried changing the method to:
def country_name
self.country = ISO3166::Country[country_code]
country.translations[I18n.locale.to_s] || country.name
end
This gives the same error as the formulation that does not use 'self'. I think these attempts don't work because the attribute in my address table is called 'country'.
When i change the method to:
def country_name
self.country = ISO3166::Country[country]
country.translations[I18n.locale.to_s] || country.name
end
I get the error with the word 'translations'. When I delete '.translations' from the method, I get an error with the word 'name'.
I'm losing my marbles trying to figure this out.
ANOTHER ATTEMPT
I tried adding the countries gem to my gem file (above country_select).
Nothing changes when I bundle this gem. Same problem.
ANOTHER ATTEMPT
Trying again (as I originally had the method defined), but with countries gem installed (above country_select gem):
def country_name
self.country = ISO3166::Country[country]
country.translations[I18n.locale.to_s] || country.name
end
I get this error: undefined method `translations' for "Cayman Islands":String
This is the same problem that I originally started with, so I don't think that adding the countries gem has helped advance toward a solution.

This code work well for me. I had country column in my User table.
On my model :-
def country_name
country = self.country
ISO3166::Country[country]
end
in the form :-
<%= form_for #user do |f| %>
<%= country_select("user", "country") %>
<% end %>

You Question is not enough, there are some point that should make error.
First,
def country_name
self.country = ISO3166::Country[country]
country.translations[I18n.locale.to_s] || country.name
end
In this methods, you should make clear country / self.country variable or instance. I can not imagine country is ISO3166::Country instance or String
Second,
Make clear it use ISO code or country name as hash key
Update:
you have country column that is string.
calling self.country = ISO3166::Country[country] means assigning ISO3166::Country instance in country(string) variable. so it makes error.
I do not know what you expect.
But you should use key of ISO03166::Country as ISO code. like ISO3166::Country['AU']. And you can not assign this in self.country.
def country_name
iso_county = ISO3166::Country[country] # `country` should be iso_code
iso_country.translations[I18n.locale.to_s] || iso_country.name
end
If you have country name, not ISO code in country column. use ISO3166::Country.find_by_name
def country_name
iso_county = ISO3166::Country.find_by_name(country) # `country` should be name like 'Australia'
iso_country.translations[I18n.locale.to_s] || iso_country.name
end

It looks like the answer will be a certain combination of your previous attempts.
Country_select uses the country codes instead of full country names:
country_select("user", "country", priority_countries: ["GB", "FR", "DE"])
This is shown on the github page for that gem:
https://github.com/stefanpenner/country_select
Since the select returns a country code then we can assume that the country attribute will be a country code and not a full country name.
With this in mind we'll modify a previous answer:
def country_name
iso_country = ISO3166::Country[country] # `country` should be code like 'AU'
iso_country.translations[I18n.locale.to_s] || iso_country.name
end
It's best practice to avoid creating a variable with the same name as an attribute in that model, so stick with iso_country instead of country in your testing.
The issue that you've been encountering is that when you assign the ISO3166::Country object to an object's attribute (self.country = ISO3166::Country[country]) it's not assigning the ISO3166::Country object itself to that attribute, but instead is assigning the ISO3166::Country object's name to it.
I tested this in Rails console:
user = User.new
#<User:0x007ff7de29d1b8
id: nil,
...
user.country = ISO3166::Country['AU']
#<ISO3166::Country:0x007ff7de3c6a08
#data=
{"continent"=>"Australia",
...
user.country
"Australia"
As you can see, it assigned the country name (not the country object) to the attribute. Therefore when you try to access country.translations or country.name it will throw an error because it is accessing the address attribute and not the country object. So definitely keep the variable name different from the attribute name.
One last thing, if you do have the country name then use the method find_country_by_name instead of find_by_name since find_by_name will return an array, while find_country_by_name will return a Country object. This should not be necessary in your current scenario, but keep it in mind if you need to use it later.

Related

How to save results into the database?

I created a web scraper.
I'm struggling to save the results into the model's columns.
How do I push the results of the scrapped results into the columns?
Do you map it? Would like to understand it so I can eventually post to index perhaps ... or show previous saved results, etc ...
Schema
ActiveRecord::Schema.define(version: 20170308223314) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "links", force: :cascade do |t|
t.string "link_info"
t.string "date"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
The two columns that I'm trying to save to are the ones you see in the schema above: link_info and date ...
Current Controller
class LinksController < ApplicationController
def craigslist_scrape
require 'open-uri'
url = "https://losangeles.craigslist.org/search/web"
page = Nokogiri::HTML(open(url))
#craigslist_info = page.css("ul.rows")
#link_info = page.css("li.result-row p.result-info a.result-title.hdrlnk")
#date = page.css("li.result-row p.result-info time.result-date")
end
end
craiglist_scrape.html.erb
<% #link_info.each_with_index do |link, index| %>
<h2><%= "Title of the job: #{link.text}" %></h2>
<p><%= "Date: #{#date[index].text}" %></p>
<% end %>
routes
Rails.application.routes.draw do
root 'links#craigslist_scrape'
end
model
class Link < ApplicationRecord
end
In the controller you can add:
def craigslist_scrape
require 'open-uri'
url = "https://losangeles.craigslist.org/search/web"
page = Nokogiri::HTML(open(url))
#craigslist_info = page.css("ul.rows")
#link_info = page.css("li.result-row p.result-info a.result-title.hdrlnk")
#date = page.css("li.result-row p.result-info time.result-date")
#link_info.each_with_index do |link, index|
Link.new(:link => link.text, :date => #date[index].text).save
end
end
I'm rather new to Ruby and don't know much about Rails but what I always do when I'm trying to save something into a "database" is I create a hash. For example:
database = {}
puts "What is your name?"
input = gets.chomp
puts "What's your age?"
input_2 = Integer(gets.chomp)
database[input.to_sym] = input_2
In the example above I create a hash called "database" and I ask the user for their name and age. Storing the user's name as the string value for database and their age as an integer value. I don't know if this helps at all, like I said I'm fairly new to Ruby.

How to add an extra value into a Rails param

I'm trying to solve a problem with getting all the values saved to my database. Here's how my application is setup is
before_filter :load_make, only: :create
def create
#record = #make.message.new(my_params)
#record.save
end
def load_make
make_id = params[:message] ? params[:message][:make_id] : params[:make_id]
#make = Car.find_by(custom_make: make_id)
#make ||= Car.find_by(id: make_id).tap
end
def my_params
params.require(:message).permit(:message_type, :message_date, :make_id)
end
My problem is that I want to create another method that does a GET request from a different API that returns a created_at date and also save it on create, similar to:
def lookup_car
Car.car_source.find(id: my_params[:make_id]).created_at
# returns a datetime value
end
I'm looking for advice on what's the best way to put this into the my_params method :message_date and save it along with everything else on the create action?
Schema for model:
create_table "messages", force: :cascade do |t|
t.integer "make_id"
t.string "message", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.string "message_type", limit: 255
t.datetime "message_date"
end
Firstly, you should NOT really change / update / insert to the created_at since Rails does that for you. If anything, I suggest you adding another column.
Secondly, you should NOT do and create / update on a Get request.
Other than that, adding another field to your params is easy as long as you have that column ready. Not sure how your models are structure, but here you can do something along the line like this below. Let's say you have message_date column in your whatever model, you can do this:
def my_params
message_date_param = { message_date: Time.now }
params.require(:message).permit(:message_type, :message_date, :make_id).merge(message_date)
end
I wrote this pretty late at night when I probably wasn't making much sense, but essentially what I was wanting was to update another value at same time save was called on the #record. I solved this by using attributes, which updates but doesn't call save (since I was doing that already below).
For example:
def create
car = Car.car_source.find(id: my_params[:make_id]).created_at
#record.attributes = { message_date: car }
#record.save
end
Also much thanks to all the other comments. I've completely refactored this code to be more rails idiomatic.

Money-Rails Gem: How to make a select list for all currencies?

I'm using the money-rails gem and want to show in my view a list of different currencies but the code I have now isn't working.
I have my Price model and the fields in_cents and currency:
create_table :prices do |t|
t.integer :in_cents, default: 0, null: false
t.string :currency, default: 'USD', null: false
Now according to the Money gem and Money-Rails docs I had to do something like:
class Price < ActiveRecord::Base
monetize :in_cents, as: "amount", with_model_currency: :in_cents_currency
def all_currencies(hash)
hash.keys
end
Than my view with simple form gem:
= f.input :currency, collection: all_currencies(Money::Currency.table)
= f.input :amount, required: false
But this gives me the error:
undefined method `all_currencies' for #<#<Class:0xd154124>:0xd15bab4>
Why?
P.S.
I want to show the ISO Code and the name like United States Dollar (USD).
Not sure this is the best solution however I made a helper as such:
def currency_codes
currencies = []
Money::Currency.table.values.each do |currency|
currencies = currencies + [[currency[:name] + ' (' + currency[:iso_code] + ')', currency[:iso_code]]]
end
currencies
end
Most simple solution:
= f.select :currency, Money::Currency.table
Your all_currencies method is an instance method, and you're not calling it on an instance.
Add self.all_currencies, and then call it with Price.all_currencies
Hope this helps

current_user association seems to work in view but not controller

The rundown: I have a user model which belongs_to a state model (region). The state model rb file is basically empty except for: has_many users. The db is seeded with the state info, and when a user registers they can select their region; all works fine.
Now I'm trying to use devise's current_user method in one of my controllers to check if the current_user's state_id is a match to one of those in the array.
class FinancesController < ApplicationController
def index
#user = current_user
if #user.state_id == ['53', '54', '60']
flash.now[:notice] = "User is from one of the accepted regions"
else
end
end
end
Schema: (the dots are just there to indicate that there's more than just the two lines)
create_table "users", force: true do |t|
.........
t.string "email", null: false
.....
.....
t.integer "state_id"
.....
end
Unfortunately it doesn't work, and will always use the "else" even if the user is of one of the accepted values in the array.
For example, the user I'm testing with has a state_id of 54. In my view I can use <%= current_user.state_id %> which will print "54" on the page.
I believe the problem may be with the array ['53', '54', '60'] because I tried just having one ID there instead of the array, and it seemed to produce the proper results. Perhaps I should be using a for loop to cycle through and check each number in the array if such is the case?
You are checking a string value against an array.
You would probably want to do this instead:
if [53, 54, 60].include? #user.state_id
Use include method:
['53', '54', '60'].include?(#user.state_id)

How to upload a text file without saving and parse the content into a database with RoR

I need to upload a text file without saving it in the database. My goal is to upload this file and automatically have to take your content and save it in my database.
my file: data.txt
name age occupation
julio 19 student
ramon 20 student
my database:
class CreateStudents < ActiveRecord::Migration
def change
create_table: students do |t|
t.string "name"
t.integer "age"
t.string "occupation"
t.timestamps
end
end
end
Does anyone have any idea how this can be done? I searched on the internet, but found no solution to my case. I need help.
= form_tag url, {multipart: true} do
= file_field_tag :file
....
in controller
if params[:file]
lines = params[:file].tempfile.readlines.map(&:chomp) #readlines from file & removes newline symbol
lines.shift #remove first line
lines.each do |l|
m = l.match(/(\S+)\s(\d+)\s(\S+)/) #parse line
Student.create {name: m[1],age: m[2], occupation: m[3]}
end
end

Resources