Calling Module Function From Model Rails - ruby-on-rails

I'm getting a method or variable not defined when trying to use an array defined in a function in a module.
Here are the files:
/lib/states.rb
module States
def fifty_states
[
'AL',
'AK',
'AZ',
'AR',
'CA',
'CO',
'CT',
'DE',
'FL',
'GA',
'HI',
'ID',
'IL',
'IN',
'IA',
'KS',
'KY',
'LA',
'ME',
'MD',
'MA',
'MI',
'MN',
'MS',
'MO',
'MT',
'NE',
'NV',
'NH',
'NJ',
'NM',
'NY',
'NC',
'ND',
'OH',
'OK',
'OR',
'PA',
'RI',
'SC',
'SD',
'TN',
'TX',
'UT',
'VT',
'VA',
'WA',
'WV',
'WI',
'WY'
]
end
end
/app/controller/player_to_team_histories_controller.rb
class PlayerToTeamHistory < ActiveRecord::Base
include States
def self.distinct_states
joins(:player).select("DISTINCT players.HometownState").where("players.HometownState IN (?)", fifty_states)
end
If I open a console I can do this just fine:
>> include States
Object
>> fifty_states
["AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL", "GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY"]

I think you're confusing class with instance here. If you want to call fifty_states from inside a class method (i.e. self.distinct_states), then you'll have to use extend, not include:
module A
def foo
"myfoo"
end
end
class B
extend A
def self.bar
foo
end
end
B.bar
#=> "myfoo"
Note however that you then cannot call the method from an instance:
b = B.new
b.bar
#=> NoMethodError: undefined method `bar' for #<B:0x007fefc4e19db0>
Here's an article with more discussion on include vs extend.
The message at the end sums things up well:
Use include for instance methods and extend for class methods. Also, it is sometimes ok to use include to add both instance and class methods. Both are really handy and allow for a great amount of code reuse. They also allow you to avoid deep inheritance, and instead just modularize code and include it where needed, which is much more the ruby way.

Related

Creating a Data Model for nested JSON parameters

I am working with the Spotify API and having trouble with the data model due to the JSON data being more complex than any other data I've seen via tutorials or courses. How would I make my struct for the "items" in this JSON data? I understand a majority of the parameters, for example, "album_group": String and "available_markets": [String] but I don't understand what to do with "artists", "external_urls", and "images". Any help would be appreciated.
Below is the first "item" from the data.
Side Note When I'm creating my struct do I have to include EVERY parameter shown below for my API call to work?
{
"href": "https://api.spotify.com/v1/artists/3qiHUAX7zY4Qnjx8TNUzVx/albums?offset=0&limit=20&include_groups=album,single,compilation,appears_on&locale=en-US,en;q=0.9",
"items": [
{
"album_group": "album",
"album_type": "album",
"artists": [
{
"external_urls": {
"spotify": "https://open.spotify.com/artist/3qiHUAX7zY4Qnjx8TNUzVx"
},
"href": "https://api.spotify.com/v1/artists/3qiHUAX7zY4Qnjx8TNUzVx",
"id": "3qiHUAX7zY4Qnjx8TNUzVx",
"name": "Yeat",
"type": "artist",
"uri": "spotify:artist:3qiHUAX7zY4Qnjx8TNUzVx"
}
],
"available_markets": [
"AD",
"AE",
"AG",
"AL",
"AM",
"AO",
"AR",
"AT",
"AU",
"AZ",
"BA",
"BB",
"BD",
"BE",
"BF",
"BG",
"BH",
"BI",
"BJ",
"BN",
"BO",
"BR",
"BS",
"BT",
"BW",
"BY",
"BZ",
"CA",
"CD",
"CG",
"CH",
"CI",
"CL",
"CM",
"CO",
"CR",
"CV",
"CY",
"CZ",
"DE",
"DJ",
"DK",
"DM",
"DO",
"DZ",
"EC",
"EE",
"EG",
"ES",
"FI",
"FJ",
"FM",
"FR",
"GA",
"GB",
"GD",
"GE",
"GH",
"GM",
"GN",
"GQ",
"GR",
"GT",
"GW",
"GY",
"HK",
"HN",
"HR",
"HT",
"HU",
"ID",
"IE",
"IL",
"IN",
"IQ",
"IS",
"IT",
"JM",
"JO",
"JP",
"KE",
"KG",
"KH",
"KI",
"KM",
"KN",
"KR",
"KW",
"KZ",
"LA",
"LB",
"LC",
"LI",
"LK",
"LR",
"LS",
"LT",
"LU",
"LV",
"LY",
"MA",
"MC",
"MD",
"ME",
"MG",
"MH",
"MK",
"ML",
"MN",
"MO",
"MR",
"MT",
"MU",
"MV",
"MW",
"MX",
"MY",
"MZ",
"NA",
"NE",
"NG",
"NI",
"NL",
"NO",
"NP",
"NR",
"NZ",
"OM",
"PA",
"PE",
"PG",
"PH",
"PK",
"PL",
"PS",
"PT",
"PW",
"PY",
"QA",
"RO",
"RS",
"RU",
"RW",
"SA",
"SB",
"SC",
"SE",
"SG",
"SI",
"SK",
"SL",
"SM",
"SN",
"SR",
"ST",
"SV",
"SZ",
"TD",
"TG",
"TH",
"TJ",
"TL",
"TN",
"TO",
"TR",
"TT",
"TV",
"TW",
"TZ",
"UA",
"UG",
"US",
"UY",
"UZ",
"VC",
"VE",
"VN",
"VU",
"WS",
"XK",
"ZA",
"ZM",
"ZW"
],
"external_urls": {
"spotify": "https://open.spotify.com/album/1x55Z0fYARLdeJVjG2UESs"
},
"href": "https://api.spotify.com/v1/albums/1x55Z0fYARLdeJVjG2UESs",
"id": "1x55Z0fYARLdeJVjG2UESs",
"images": [
{
"height": 640,
"url": "https://i.scdn.co/image/ab67616d0000b273b20fdc3ee4c262693cfdf005",
"width": 640
},
{
"height": 300,
"url": "https://i.scdn.co/image/ab67616d00001e02b20fdc3ee4c262693cfdf005",
"width": 300
},
{
"height": 64,
"url": "https://i.scdn.co/image/ab67616d00004851b20fdc3ee4c262693cfdf005",
"width": 64
}
],
"name": "Up 2 Më",
"release_date": "2021-09-10",
"release_date_precision": "day",
"total_tracks": 22,
"type": "album",
"uri": "spotify:album:1x55Z0fYARLdeJVjG2UESs"
}
]
}
Good rule of thumb is, that you need a separate Decodable object for every JSON object (marked with {}) when working with such API.
You can nest Decodable structs. Just don't forget that here, ExternalUrl has to be Decodable too, otherwise you will get an error
struct SpotifyItem: Decodable {
// ...
let artists: [Artist]
let images: [SpotifyImage]
// ...
}
struct Artist: Decodable {
let external_urls: ExternalUrl
let href: String
let id: String
// ...
}
struct ExternalUrl: Decodable {
let spotify: String
}
struct SpotifyImage: Decodable {
let url: String
let height: Int
let width: Int
}
Side Note Answer: I would advise you to do so. It seems like there are ways to decode JSON elements into swift dictionary, but it goes against the type safety that you are trying to achieve. You are parsing JSON into Decodable structs precisely because you want to know ahead of time what are you dealing with.
Don't forget you can still do optional attributes, for example you could have
struct ExternalUrl: Decodable {
let spotify: String
let instagram: String?
}
In that case, if instagram is not present, it will be set to nil. My advice would be follow this more modern and safe way of doing things. You can of course parse the entire JSON into a huge [String: Any] object, but you will have to write out everything you want to access anyway. Plus there will be a LOT of type casting and checks, which Codable does for you.

Ruby: Should strings in a frozen array also be individually frozen?

Ruby 2.2.3, Rails 4.2.1
I have a large number of arrays of strings that I define as constants for use throughout an application. They are various sets of ISO country codes, language codes, that sort of thing, so two to four characters, numbered in the hundreds of unique values each.
The different arrays are collections of these, so NORTH_AMERICA_COUNTRY_CODES might be an array of a dozen of so country codes, AFRICA_COUNTRY_CODES might be an array of around 60. Many of them overlap (various versions of the Commonwealth countries, for example).
The arrays are used in comparison with other arbitrary arrays of country codes for logic such as "Subtract this list of countries from Africa".
So I'm wondering whether, when I generate these constants, I ought to freeze the strings within the arrays, so instead of:
WORLD_COUNTRIES = Countries.pluck(:country_code).freeze
... maybe ...
WORLD_COUNTRIES = Countries.pluck(:country_code).map{|c| c.freeze}.freeze
Is there a way of quantifying the potential benefits?
I considered using arrays of symbols instead of arrays of strings, but the arbitrary arrays that these are used with are stored in PostgreSQL text arrays, and it seems like I'd need to serialise those columns instead, or maybe override the getter and setter methods to change the values between arrays of strings and arrays of symbols. Ugh.
Edit
Testing results, in which I've attempted to benchmark three situations:
Comparing a frozen array of unfrozen strings with an unfrozen array of unfrozen strings
Comparing a frozen array of frozen strings with an unfrozen array of unfrozen strings
Comparing a frozen array of symbols with an unfrozen array of symbols (in case I bit the bullet and went all symbolic on this).
Any thoughts on methodology or interpretation gratefully received. I'm not sure whether the similarity in results between the first two indicates that they are in all respects the same, but I'd be keen on anything that can directly point to differences in memory allocation.
Script:
require 'benchmark'
country_list = ["AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CS", "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", "WS", "YE", "YT", "YU", "ZA", "ZM", "ZW"]
FROZEN_ARRAY = country_list.dup.freeze
puts FROZEN_ARRAY.size
FROZEN_ARRAY_AND_STRINGS = country_list.dup.map{|x| x.freeze}.freeze
FROZEN_ARRAY_AND_SYMBOLS = country_list.dup.map{|x| x.to_sym}.freeze
comp_s = %w(AD AT BE CY EE FI FR DE ES GR IE IT LU LV MC ME MT NL PT SI SK SM VA)
comp_sym = %w(AD AT BE CY EE FI FR DE ES GR IE IT LU LV MC ME MT NL PT SI SK SM VA).map{|x| x.to_sym}
Benchmark.bm do |x|
x.report("frozen string" ) { 10000.times {|i| c = (FROZEN_ARRAY_AND_STRINGS & comp_s.dup) }}
x.report("unfrozen string") { 10000.times {|i| c = (FROZEN_ARRAY & comp_s.dup) }}
x.report("symbols" ) { 10000.times {|i| c = (FROZEN_ARRAY_AND_SYMBOLS & comp_sym.dup) }}
end
Benchmark.bmbm do |x|
x.report("frozen string" ) { 10000.times {|i| c = (FROZEN_ARRAY_AND_STRINGS & comp_s.dup) }}
x.report("unfrozen string") { 10000.times {|i| c = (FROZEN_ARRAY & comp_s.dup) }}
x.report("symbols" ) { 10000.times {|i| c = (FROZEN_ARRAY_AND_SYMBOLS & comp_sym.dup) }}
end
Result:
2.2.3 :001 > require 'benchmark'
=> false
2.2.3 :002 >
2.2.3 :003 > country_list = ["AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CS", "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", "WS", "YE", "YT", "YU", "ZA", "ZM", "ZW"]
=> ["AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CS", "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", "WS", "YE", "YT", "YU", "ZA", "ZM", "ZW"]
2.2.3 :004 > FROZEN_ARRAY = country_list.dup.freeze
=> ["AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CS", "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", "WS", "YE", "YT", "YU", "ZA", "ZM", "ZW"]
2.2.3 :005 > puts FROZEN_ARRAY.size
252
=> nil
2.2.3 :006 > FROZEN_ARRAY_AND_STRINGS = country_list.dup.map{|x| x.freeze}.freeze
=> ["AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CS", "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", "WS", "YE", "YT", "YU", "ZA", "ZM", "ZW"]
2.2.3 :007 > FROZEN_ARRAY_AND_SYMBOLS = country_list.dup.map{|x| x.to_sym}.freeze
=> [:AD, :AE, :AF, :AG, :AI, :AL, :AM, :AN, :AO, :AQ, :AR, :AS, :AT, :AU, :AW, :AX, :AZ, :BA, :BB, :BD, :BE, :BF, :BG, :BH, :BI, :BJ, :BL, :BM, :BN, :BO, :BQ, :BR, :BS, :BT, :BV, :BW, :BY, :BZ, :CA, :CC, :CD, :CF, :CG, :CH, :CI, :CK, :CL, :CM, :CN, :CO, :CR, :CS, :CU, :CV, :CW, :CX, :CY, :CZ, :DE, :DJ, :DK, :DM, :DO, :DZ, :EC, :EE, :EG, :EH, :ER, :ES, :ET, :FI, :FJ, :FK, :FM, :FO, :FR, :GA, :GB, :GD, :GE, :GF, :GG, :GH, :GI, :GL, :GM, :GN, :GP, :GQ, :GR, :GS, :GT, :GU, :GW, :GY, :HK, :HM, :HN, :HR, :HT, :HU, :ID, :IE, :IL, :IM, :IN, :IO, :IQ, :IR, :IS, :IT, :JE, :JM, :JO, :JP, :KE, :KG, :KH, :KI, :KM, :KN, :KP, :KR, :KW, :KY, :KZ, :LA, :LB, :LC, :LI, :LK, :LR, :LS, :LT, :LU, :LV, :LY, :MA, :MC, :MD, :ME, :MF, :MG, :MH, :MK, :ML, :MM, :MN, :MO, :MP, :MQ, :MR, :MS, :MT, :MU, :MV, :MW, :MX, :MY, :MZ, :NA, :NC, :NE, :NF, :NG, :NI, :NL, :NO, :NP, :NR, :NU, :NZ, :OM, :PA, :PE, :PF, :PG, :PH, :PK, :PL, :PM, :PN, :PR, :PS, :PT, :PW, :PY, :QA, :RE, :RO, :RS, :RU, :RW, :SA, :SB, :SC, :SD, :SE, :SG, :SH, :SI, :SJ, :SK, :SL, :SM, :SN, :SO, :SR, :SS, :ST, :SV, :SX, :SY, :SZ, :TC, :TD, :TF, :TG, :TH, :TJ, :TK, :TL, :TM, :TN, :TO, :TR, :TT, :TV, :TW, :TZ, :UA, :UG, :UM, :US, :UY, :UZ, :VA, :VC, :VE, :VG, :VI, :VN, :VU, :WF, :WS, :YE, :YT, :YU, :ZA, :ZM, :ZW]
2.2.3 :008 > comp_s = %w(AD AT BE CY EE FI FR DE ES GR IE IT LU LV MC ME MT NL PT SI SK SM VA)
=> ["AD", "AT", "BE", "CY", "EE", "FI", "FR", "DE", "ES", "GR", "IE", "IT", "LU", "LV", "MC", "ME", "MT", "NL", "PT", "SI", "SK", "SM", "VA"]
2.2.3 :009 > comp_sym = %w(AD AT BE CY EE FI FR DE ES GR IE IT LU LV MC ME MT NL PT SI SK SM VA).map{|x| x.to_sym}
=> [:AD, :AT, :BE, :CY, :EE, :FI, :FR, :DE, :ES, :GR, :IE, :IT, :LU, :LV, :MC, :ME, :MT, :NL, :PT, :SI, :SK, :SM, :VA]
2.2.3 :010 >
2.2.3 :011 >
2.2.3 :012 > Benchmark.bm do |x|
2.2.3 :013 > x.report("frozen string" ) { 10000.times {|i| c = (FROZEN_ARRAY_AND_STRINGS & comp_s.dup) }}
2.2.3 :014?> x.report("unfrozen string") { 10000.times {|i| c = (FROZEN_ARRAY & comp_s.dup) }}
2.2.3 :015?> x.report("symbols" ) { 10000.times {|i| c = (FROZEN_ARRAY_AND_SYMBOLS & comp_sym.dup) }}
2.2.3 :016?> end
user system total real
frozen string 0.190000 0.000000 0.190000 ( 0.194141)
unfrozen string 0.170000 0.010000 0.180000 ( 0.174675)
symbols 0.080000 0.000000 0.080000 ( 0.081507)
=> [#<Benchmark::Tms:0x007f810c3aca70 #label="frozen string", #real=0.1941408810671419, #cstime=0.0, #cutime=0.0, #stime=0.0, #utime=0.1899999999999995, #total=0.1899999999999995>, #<Benchmark::Tms:0x007f810c82b538 #label="unfrozen string", #real=0.1746752569451928, #cstime=0.0, #cutime=0.0, #stime=0.010000000000000009, #utime=0.16999999999999993, #total=0.17999999999999994>, #<Benchmark::Tms:0x007f810af2cfa0 #label="symbols", #real=0.08150708093307912, #cstime=0.0, #cutime=0.0, #stime=0.0, #utime=0.08000000000000007, #total=0.08000000000000007>]
2.2.3 :017 >
2.2.3 :018 >
2.2.3 :019 > Benchmark.bmbm do |x|
2.2.3 :020 > x.report("frozen string" ) { 10000.times {|i| c = (FROZEN_ARRAY_AND_STRINGS & comp_s.dup) }}
2.2.3 :021?> x.report("unfrozen string") { 10000.times {|i| c = (FROZEN_ARRAY & comp_s.dup) }}
2.2.3 :022?> x.report("symbols" ) { 10000.times {|i| c = (FROZEN_ARRAY_AND_SYMBOLS & comp_sym.dup) }}
2.2.3 :023?> end
Rehearsal ---------------------------------------------------
frozen string 0.180000 0.000000 0.180000 ( 0.183846)
unfrozen string 0.200000 0.000000 0.200000 ( 0.196311)
symbols 0.080000 0.000000 0.080000 ( 0.082794)
------------------------------------------ total: 0.460000sec
user system total real
frozen string 0.160000 0.000000 0.160000 ( 0.167051)
unfrozen string 0.170000 0.000000 0.170000 ( 0.171601)
symbols 0.080000 0.000000 0.080000 ( 0.078746)
=> [#<Benchmark::Tms:0x007f811022a388 #label="frozen string", #real=0.1670510449912399, #cstime=0.0, #cutime=0.0, #stime=0.0, #utime=0.16000000000000014, #total=0.16000000000000014>, #<Benchmark::Tms:0x007f811022a4c8 #label="unfrozen string", #real=0.17160122003406286, #cstime=0.0, #cutime=0.0, #stime=0.0, #utime=0.16999999999999993, #total=0.16999999999999993>, #<Benchmark::Tms:0x007f8108eb1c58 #label="symbols", #real=0.07874645793344826, #cstime=0.0, #cutime=0.0, #stime=0.0, #utime=0.08000000000000007, #total=0.08000000000000007>]
2.2.3 :024 >
2.2.3 :025 >
2.2.3 :026 >
Since you're dealing with a small number of values, and since the performance benefits of symbols are evident from your testing, just go with symbols.
BTW, you can use map(&:to_sym) instead of map {|x| x.to_sym}.

Convert Json structures(Json File) to Ruby hash

I have a JSON File which looks like:
{"business_id": "vcNAWiLM4dR7D2nwwJ7nCA", "full_address": "4840 E Indian School Rd\nSte 101\nPhoenix, AZ 85018", "hours": {"Tuesday": {"close": "17:00", "open": "08:00"}, "Friday": {"close": "17:00", "open": "08:00"}, "Monday": {"close": "17:00", "open": "08:00"}, "Wednesday": {"close": "17:00", "open": "08:00"}, "Thursday": {"close": "17:00", "open": "08:00"}}, "open": true, "categories": ["Doctors", "Health & Medical"], "city": "Phoenix", "review_count": 7, "name": "Eric Goldberg, MD", "neighborhoods": [], "longitude": -111.98375799999999, "state": "AZ", "stars": 3.5, "latitude": 33.499313000000001, "attributes": {"By Appointment Only": true}, "type": "business"}
{"business_id": "JwUE5GmEO-sH1FuwJgKBlQ", "full_address": "6162 US Highway 51\nDe Forest, WI 53532", "hours": {}, "open": true, "categories": ["Restaurants"], "city": "De Forest", "review_count": 26, "name": "Pine Cone Restaurant", "neighborhoods": [], "longitude": -89.335843999999994, "state": "WI", "stars": 4.0, "latitude": 43.238892999999997, "attributes": {"Take-out": true, "Good For": {"dessert": false, "latenight": false, "lunch": true, "dinner": false, "breakfast": false, "brunch": false}, "Caters": false, "Noise Level": "average", "Takes Reservations": false, "Delivery": false, "Ambience": {"romantic": false, "intimate": false, "touristy": false, "hipster": false, "divey": false, "classy": false, "trendy": false, "upscale": false, "casual": false}, "Parking": {"garage": false, "street": false, "validated": false, "lot": true, "valet": false}, "Has TV": true, "Outdoor Seating": false, "Attire": "casual", "Alcohol": "none", "Waiter Service": true, "Accepts Credit Cards": true, "Good for Kids": true, "Good For Groups": true, "Price Range": 1}, "type": "business"}
I want to to traverse this file and convert individual structures into a Ruby hash so that I can access the different hashes to perform actions on them.
This is a hash of the first structure of the JSON file:
{"business_id"=>"uUsfpN81JCMKyH6c0D0bTg", "full_address"=>"1910 Village Center Cir\nSte 6\nSummerlin\nLas Vegas, NV 89134", "hours"=>{}, "open"=>true, "categories"=>["Food", "Desserts", "Italian", "Pizza", "Restaurants"], "city"=>"Las Vegas", "review_count"=>6, "name"=>"Rocco's NY Pizza & Pasta", "neighborhoods"=>["Summerlin"], "longitude"=>-115.3041999, }
Similarly I want to have hashes of all other JSON structures. How can I do this in Ruby on Rails?
The file you got it's not a JSON file, but simply a text file with a bunch of JSON-encoded objects. Simply loop over the lines.
require 'json'
File.read("file.txt").split("\n").each do |line|
JSON.parse(line)
end
or use .map instead of .each if you want return an Array of Hash.
File.foreach("file.txt").map do |line|
JSON.parse(line)
end
File.read("file.txt").split("\n").map do |line|
JSON.parse(line)
end
Also note this has nothing to do with Rails, it's pure Ruby code.
Something like this will return an array of hashes retrieved from the file:
require 'json'
array_of_hashes = File.foreach('file.txt').map{ |l| JSON[l] }
Be warned though, this isn't scalable because you'll be pulling the entire file into memory, and if that resulting array is larger than the available space you'll see your program go to a crawl.
A more scalable approach would be to read each line, convert it to a Ruby object using JSON's parser, then immediately stuff the resulting object into a database record, and finally iterate over the database to do your massaging. SQLite3, PostgreSQL, MySQL would all be reasonable choices for this.
Have you tried JSON.parse
require 'json'
JSON.parse(your_json) #=> your expected result

Format a complex mixture of hashes and array and put out an end result as an array

After a lot of fixing an old app, i am now left with this
#products = {:parent_products=>[["Product title", "Product body 1", "3", "user1"], ["Product title 2", "Product body 2", "5", "user_2"]], :child_products=>[["making a reply", "user_2", "3", "4"], ["yes i read it", "user_5", "5", "6"], ["me too indeed. hurray", "user_1", "4", "7"], ["great to see this", "user_7", "3", "8"]]}
Now what I cant figure out how to do is -
Format #products[:child_products] in such a way that, if lets say #products[:child_products][2][2] includes in any of the elements in any of the elements with the inner index being [3] it should be appended to that array's last index, as the array itself.
So for example, from the above given hash we can see that
value of #products[:child_products][2][2] does have one another like it, at #products[:child_products][0][3] (which is the integer 4). Since it is being met true the new array should now look like
["making a reply","user_2", "3", "4", ["me too indeed. hurray","user_1", "4", "7"]]
Now note: the above is just an example. In other words inside the #products[:child_products] a search should made in a search a way that it looks
#products[:child_products][<any index>][2] inside #products[:child_products][<any index>][3]
Hope I am making sense so far.
Moving on, if the condition is not met or even met (as mentioned in the first point) and the arrays inside #products[:child_products] have been rearranged, it should now run another logic where
#products[:child_products][<any index>][2] should look for #product[:parent_products][<any index>][2]
And once the condition is met it should append that entire array into the array of #product[:parent_products]
So for example #product[:child_products][1][2] matches #product[:parent_products][1][2] (which is 5) then the new array set (of the respective index position) inside #product[:parent_products] should look like
["Product title 2", "Product body 2", "5", "user_2",["yes i read it", "user_5", "5", "6"]]
Thats all.
I tried my best to make sure I am stating the output clear. If you have any questions please ask.
UPDATE: I see that above is creating confusion. So here is the quick glance on what i have and what i want
What I have
#products = {:parent_products=>[["Product title", "Product body 1", "3", "user1"], ["Product title 2", "Product body 2", "5", "user_2"]], :child_products=>[["making a reply", "user_2", "3", "4"], ["yes i read it", "user_5", "5", "6"], ["me too indeed. hurray", "user_1", "4", "7"], ["great to see this", "user_7", "3", "8"]]}
And the final output I want is an array with the formatted data
[["Product title", "Product body 1", "3", "user1", ["making a reply", "user_2", "3", "4", ["me too indeed. hurray", "user_1", "4", "7"]], ["great to see this", "user_7", "3", "8"] ], ["Product title 2", "Product body 2", "5", "user_2", ["yes i read it", "user_5", "5", "6"]]]
Thanks
#products = {:parent_products=>[["Product title", "Product body 1", "3", "user_1"], ["Product title 2", "Product body 2", "5", "user_2"]],
:child_products=>[["making a reply", "user_2", "3", "4"], ["yes i read it", "user_5", "5", "6"],
["me too indeed. hurray", "user_1", "4", "7"], ["great to see this", "user_7", "3", "8"]]}
arr = #products[:parent_products].flat_map do |i|
i << #products[:child_products].select{|j| j.any? {|m| m == i[-1] || m == i[-2]}}
end
arr
# => ["Product title",
# "Product body 1",
# "3",
# "user_1",
# [["making a reply", "user_2", "3", "4"],
# ["me too indeed. hurray", "user_1", "4", "7"],
# ["great to see this", "user_7", "3", "8"]],
# "Product title 2",
# "Product body 2",
# "5",
# "user_2",
# [["making a reply", "user_2", "3", "4"],
# ["yes i read it", "user_5", "5", "6"]]]
class Product
attr_reader :description, :body, :id, :child_id
def initialize description, body, id, child_id
#description, #body, #id, #child_id = description, body, id, child_id
#children = []
end
def append child
#children << child
end
def accepts? child
child.id == self.id
end
def to_a
[description, body, id, child_id] + #children.map(&:to_a)
end
end
class ChildProduct < Product
def accepts? child
self.child_id == child.id
end
end
class ProductTransformer
def initialize products
#products = products
end
def transform
parents = #products[:parent_products].map{|e| Product.new *e}
children = #products[:child_products].map{|e| ChildProduct.new *e}
children.each do |child|
p = parents.detect{|parent| parent.accepts? child}
p.append child if p
children.each do |another|
next if another === child
child.append another if child.accepts?(another)
end
end
parents.map(&:to_a)
end
end
products = {
:parent_products=>[
["Product title", "Product body 1", "3", "user1"],
["Product title 2", "Product body 2", "5", "user_2"]],
:child_products=>[
["making a reply", "user_2", "3", "4"],
["yes i read it", "user_5", "5", "6"],
["me too indeed. hurray", "user_1", "4", "7"],
["great to see this", "user_7", "3", "8"]]
}
ProductTransformer.new(products).transform

ASP MVC3 - set drop down list for multiple items

On the edit page of my ASP MVC3 site, there is a portion of the code that deals with a dynamic number of agents attached to each model item. The code below is used to create the exact amount of table rows to display
#for (int i = 0; i < Model.Fixed.Count; i++)
{
if(!String.IsNullOrWhiteSpace(Model.Fixed[i].AgentId))
{
<tr>
<td>
#Html.DropDownListFor(model => model.Fixed[i].StateCode,
(SelectList)ViewBag.StateCodeList)
</td>
<td>
#Html.EditorFor(model => model.Fixed[i].AgentId)
#Html.ValidationMessageFor(model => model.Fixed[i].AgentId)
</td>
</tr>
}
}
Unfortunately, I am not sure how to display the property state code associated with each agent. Right now this code is the only area in the controller that deals with setting the drop down list values:
SelectList tmpList = new SelectList(new[] { "AL", "AK", "AS", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FM", "FL", "GA", "GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MH", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NA", "NM", "NY", "NC", "ND", "MP", "OH", "OK", "OR", "PW", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "US", "VT", "VI", "VA", "WA", "WV", "WI", "WY" });
ViewBag.StateCodeList = tmpList;
Since there could be anywhere from 0 to 100 agents, the problem I am running into is displaying the proper state code associated with that particular agent in their respective drop down boxes.
I wound up finding the overload for this pretty quick:
<td>
#Html.DropDownListFor(model => model.Fixed[i].StateCode,
(SelectList)ViewBag.StateCodeList, Model.Fixed[i].StateCode)
</td>

Resources