How to map using constants in Ruby - ruby-on-rails

I'm trying to map an array of custom values using a constant that's already defined. I'm running into some problems.
This works perfectly:
Car.where(brand: car_brands.map(&:Car_honda))
Although I have all the car brands already defined in my file, so I would prefer to use the constants over rewriting the names. For example:
HONDA = "Car_honda"
When I try and map this constant to the array it doesn't seem to work properly:
Car.where(brand: car_brands.map(&:HONDA))
I tried to use a block with map, but I still got the same result:
Car.where(brand: car_brands.map {|c| c.HONDA}))
Are we able to use constants with map?

Just use send:
Car.where(brand: car_brands.map { |c| c.send(HONDA) })

I'm not sure where you're going with this, or precisely where you're coming from, but here's an example that follows Rails conventions:
class Brand < ActiveRecord::Base
has_many :cars
end
class Car < ActiveRecord::Base
belongs_to :brand
end
Then you can find all cars associated with the "Honda" brand:
Brand.find_by(name: 'Honda').cars
Or find all cars matching one or more arbitrary brand names using a JOIN operation:
Car.joins(:brand).where(brand: { name: %w[ Honda Ford ] })
If you go with the flow in Rails things are a lot easier.

Are you able to use constants with map?
Nope. Not like this, anyhow.
car_brands.map { |c| c.HONDA }
This means, for every thing in car_brands call method HONDA on it and return results of the invocations. So, unless you have method HONDA defined (which you absolutely shouldn't), this has no chance to work.

Constants are defined on the class, not on the object. You can invoke them through .class.
:005 > Integer.const_set('ABC', 1)
=> 1
:006 > Integer::ABC
=> 1
:007 > [1,2,3].map {|i| i.class::ABC}
=> [1, 1, 1]
This will not work for your use case unless car_brands contains an array of different Car classes.

First off, you probably don't want to things this way. Perhaps it's the way your example is worded, but the only way it makes sense, as I'm reading it, is if Car_brands is an array of classes. And if that's the case, if doesn't make sense to have a constant called HONDA. If anything, you would have a constant called BRAND that might equal "Honda" for a given class. I strongly recommend you rethink your data structures before moving forward.
All that said, you can use const_get to access constants using map. e.g.
class CarBrand1
BRAND = 'Honda'
end
class CarBrand2
BRAND = 'Toyota'
end
car_brands = [CarBrand1, CarBrand2]
car_brands.map{|car_brand| car_brand.const_get("BRAND")}
car_brands.map{|car_brand| car_brand::BRAND} # Alternatively
# => ["Honda", "Toyota"]

Related

Querying associated records using Active Record

I have a model Tippani, which belongs to another model Fiscal Year.
Tippani.rb
class QuotationTippani < ApplicationRecord
belongs_to :fiscal_year
end
I have two instances of tippani class, that belong to the same fiscal year.
I want to get the instances of tippani class that belongs to the same fiscal year.
I tried something like this:
Tippani.where(fiscal_year_id == 4)
But I get an error
undefined local variable or method `fiscal_year_id' for main:Object
Also, is there some query method, where I could get all the tippani instances, whose fiscal year is less than 2073 or some number.
Something like this:
Tippani.fiscal_year.where(year < 2074)
You need to pass Hash into your query method, like this:
QuotationTippani.where(fiscal_year_id: 4)
or, if you have FiscalYear instance available and Fiscalyear#quotation_tippanies association is set up:
fiscal_year.quotation_tippanies
About the filtering by year, it's about using joins and passing parameters properly:
QuotationTippani.joins(:fiscal_year).where('fiscal_years.year < ?', 2074)
Generally speaking, I advice you to read this guide:
https://guides.rubyonrails.org/active_record_querying.html
You'll find all the information you need here.
== is the comparison operator. When you write:
Tippani.where(fiscal_year_id == 4)
Ruby treats fiscal_year_id as an identifier and tries to find either a local variable or a method named fiscal_year_id and checks if it is equal to 4. So even if you had assigned fiscal_year_id you're calling:
Tippani.where(true) # or false
Which isn't even close since you want evaluate WHERE tippanis.fiscal_year_id = 4 in the database.
Hashes in Ruby use hashrockets (=>) or colons (:):
# you can assign any type of key with hashrockets
{ :foo => :bar, 1 => "first", true => "yes", false => "no" }
# colons coerce the keys to symbols
{ a: 1, b: 2, c: 3, "this gets cast to a symbol": 4 }
Colons are generally preferred. When you call a method you can omit the braces as long as the hash is the last argument.
Tippani.where({ :fiscal_year_id => 4 }) # lots of noise
Tippani.where(fiscal_year_id: 4) # better
Also, is there some query method, where I could get all the tippani
instances, whose fiscal year is less than 2073 or some number.
Something like this:
Tippani.fiscal_year.where(year < 2074)
Again this won't work since < is an operator and the result of evaluating the expression is true or false.
ActiveRecord does not really have an elegant way of handling LT, GT, LTE and GTE conditions. So you either need to use a string:
Tippani.joins(:fiscal_year).where('fiscal_years.year < ?', 2074)
Or use Arel:
Tippani.joins(:fiscal_year).where(FiscalYear.arel_table[:year].lt(2074))
You need joins
Tippani.joins(:fiscal_year).where(fiscal_years: { id: 4 })
Second question also can be handled in similar way.
Tippani.joins(:fiscal_year).where("fiscal_years.year < ?", 2074)
Hope that helps!

Alias find_by queries of often used objects

I have a Mood model which contains several moods which are seeded to the database, then they are never changed.
So, I was wondering, instead of always doing Mood.find_by(name: 'happy'), can't I somehow alias this to something like happy, and have it available everywhere where I would have the model available? Is there something in terms of Rails/ActiveRecord that allows this?
You can use Ruby's define_singleton_method in moods.rb. I didn't load ActiveRecord, so just replace the puts "Call Mood..." with the actual call:
class Mood
##moods = ['angry', 'calm', 'happy']
##moods.each { |mood| define_singleton_method(mood.to_sym) { puts "Call Mood.find_by(name: #{mood}) here" } }
end
Here's the output:
2.2.1 :003 > load 'moods.rb'
Call Mood.find_by(name: calm) here
Call Mood.find_by(name: angry) here
=> true
Defining a [] method is done sometimes for this.
class Mood < ActiveRecord::Base
def self.[](mood_name)
find_by_name(mood_name)
end
end
Then you can refer to it as Mood[:happy].

How to map a model's integer attribute to a string?

I have a Hotels table in my database, and one of the columns is :status (integer). I'm looking to convert these integers into strings, so 1 = "Awaiting Contract", 2 = "Designing" and so on...
I have searched Stack for some answers, and the lack of them makes me think that I'm coming at this problem from the wrong angle? I used to do this in PHP whilst pulling the data. New-ish to Rails so any help, or best practise advice would be much appreciated.
Check enum of ActiveRecord - doc.
Here you can configure your :status:
class Hotel < ActiveRecord::Base
enum status: { waiting_contract: 1, designing: 2 }
def format_status
status.to_s.humanize
end
end
It'll create methods like this:
hotel.waiting_contract?
hotel.designing?
hotel.waiting_contract!
hotel.format_status # => "Waiting contract"
Hope that helps!
UPDATE
Similar functionality might be achieved by overriding the status method itself, although having separate methods is more advised:
class Hotel < ActiveRecord::Base
enum status: { waiting_contract: 1, designing: 2 }
def status
super.to_s.humanize
end
end
Furthermore, decorators are something you should look into for view-specific methods.
It depends what you need the list for. An alternative to the above ideas, is to create a hash. Hashes are very Ruby and designed just for this sort of paired data.
Create the hash, (enumeration typing is automatic.)
my_h = { "waiting" => 1, "design" => 2 }
Then to access
my_h["waiting"] = 1
There's much more you can do with hashes. This is just the simplest case.
A hash may or not fulfill your needs, but it's a splendid tool that comes with a nice set of Ruby worker methods.
http://ruby-doc.org/core-2.2.0/Hash.html

Struct with types and conversion

I am trying to accomplish the following in Ruby:
person_struct = StructWithType.new "Person",
:name => String,
:age => Fixnum,
:money_into_bank_account => Float
And I would like it to accept both:
person_struct.new "Some Name",10,100000.0
and
person_struct.new "Some Name","10","100000.0"
That is, I'd like it to do data conversion stuff automatically.
I know Ruby is dinamically and I should not care about data types but this kind of conversion would be handy.
What I am asking is something similar to ActiveRecord already does: convert String to thedatatype defined in the table column.
After searching into ActiveModel I could not figure out how to to some TableLess that do this conversion.
After all I think my problem may require much less that would be offered by ActiveModel modules.
Of course I could implement a class by myself that presents this conversion feature, but I would rather know this has not yet been done in order to not reinvent the wheel.
Tks in advance.
I think that the implementation inside a class is so easy, and there is no overhead at all, so I don't see the reason to use StructWithType at all. Ruby is not only dynamic, but very efficient in storing its instances. As long as you don't use an attribute, there is none.
The implementation in a class should be:
def initialize(name, age, money_into_bank_account)
self.name = name
self.age = age.to_i
self.money_into_bank_account = money_into_bank_account.to_f
end
The implementation in StructWithType would then be one layer higher:
Implement for each type a converter.
Bind an instance of that converter in the class.
Use in the new implementation of StructWithType instances (not class) the converters of the class to do the conversion.
A very first sketch of it could go like that:
class StructWithType
def create(args*)
<Some code to create new_inst>
args.each_with_index do |arg,index|
new_value = self.converter[index].convert(arg)
new_inst[argname[index]]= new_value
end
end
end
The ideas here are:
You have an instance method named create that creates from the factory a new struct instance.
The factory iterates through all args (with the index) and searches for each arg the converter to use.
It converts the arg with the converter.
It stores in the new instance at the argname (method argname[] has to be written) the new value.
So you have to implement the creation of the struct, the lookup for converter, the lookup for the argument name and the setter for the attributes of the new instance. Sorry, no more time today ...
I have used create because new has a different meaning in Ruby, I did not want to mess this up.
I have found a project in github that fulfill some of my requirements: ActiveHash.
Even though I still have to create a class for each type but the type conversion is free.
I am giving it a try.
Usage example:
class Country < ActiveHash::Base
self.data = [
{:id => 1, :name => "US"},
{:id => 2, :name => "Canada"}
]
end
country = Country.new(:name => "Mexico")
country.name # => "Mexico"
country.name? # => true

How do you iterate over active record objects in Ruby On Rails?

This question is quite simple but I have run into the problem a few times.
Let's say you do something like:
cars = Vehicle.find_by_num_wheels(4)
cars.each do |c|
puts "#{c.inspect}"
end
This works fine if cars is an array but fails if there is only one car in the database. Obviously I could do something like "if !cars.length.nil?" or check some other way if the cars object is an array before calling .each, but that is a bit annoying to do every time.
Is there something similar to .each that handles this check for you? Or is there an easy way to force the query result into an array regardless of the size?
You might be looking for
cars = Vehicle.find_all_by_num_wheels(4)
The dynamic find_by_ methods only return one element and you have to use find_all_by_ to return multiple.
If you always want all of the cars, you should use find_all instead:
cars = Vehicle.find_all_by_num_wheels(4)
You could also turn a single Vehicle into an array with:
cars = [cars] unless cars.respond_to?(:each)
Named scoped version for your problem
Vehicle.scoped(:conditions => { :num_wheels => 4 } ).each { |car| car.inspect }
You can do this to get arrays everytimes :
cars = Vehicle.find(:all, :conditions => {num_wheels => 4})
I don't think that you have a loop that will check if the object is an array.
Another solution could be:
for i in (1..cars.lenght)
puts cars[i].inspect
end
(haven't tested, it might break to test the lenght on a string. Let me know if it does)

Resources