Rails 4 - Country Select & Simple Form

2019-08-20 22:51发布

问题:

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.

回答1:

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 %>


回答2:

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


回答3:

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.