Querying associated records using Active Record - ruby-on-rails

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!

Related

Rails use attributes as string in method

I am using the gem Alchemist to make unit conversions.
Given this working in my model:
class Item < ApplicationRecord
def converted
quantity = 1
quantity.kg.to.g
end
end
How do I make kg and g dynamic? Like:
quantity.unit_purchase.to.unit_inventory
unit_purchase and unit_inventory are attributes (strings) of the class, corresponding to values such as kg, g and so on.
So perhaps something like:
x = self.unit_purchase
y = self.unit_inventory
quantity.x.to.y
But I'm having hard time to find the syntax.
If you really want to do this the hard way:
unit_purchase = :kg
unit_inventory = :g
quantity.send(unit_purchase).to.send(unit_inventory)
That depends on knowing with absolute certainty that the two arguments are valid and aren't something hostile supplied by the user.
A safer way is to define a more arbitrary conversion method like:
quantity.convert(from_unit: unit_purchase, to_unit: unit_inventory)
Where that can check the arguments accordingly and raise on unexpected values.

How to map using constants in Ruby

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"]

How to compare objects in array RUBY

How to compare objects with the same parameter in ruby? How to define elsif part?
def compare
array_of_items = #items.map(&:object_id)
if array_of_items.uniq.size == array_of_items.size #array has only uniq vlaues - it's not possible to duplicate object - good!
return
elsif
#the comparision of objects with the same object_id by other param (i.e. date_of_lease param). The part I can not formulate
else
errors.add('It is not possible to purchase many times one item with the same values')
end
end
You can use Enumerable#group_by, e.g.
elsif #items.group_by(&:date_of_lease).count == array_of_items.size
As far as I understand, I guess you want to compare two objects with same object_id.
same_objects = #items.select { | element |
if array_of_items.count(element.object_id) > 1 do
# Duplicate object
end
}
I don't know how about other Ruby implementations, but in MRI Object#object_id returns unique (integer representation of the object in memory) value for every object. If you try to redefine it, you will get the warning:
class Object
def object_id
'a'
end
end
#=> warning: redefining `object_id' may cause serious problems
:object_id
First of all, since this is tagged as rails, isn't this the type of thing you can solve with a built in validation?
validates_uniqueness_of :date_of_lease, scope: :object_id
I don't know know about your implementation, but if you used the primary key of your database you might not even need that scope.
Otherwise, assuming you have overriden ruby object_id so two objects can have the same ids (¿?) I can only think of something complex like:
def compare
duplicate_items = #items.group_by(&:object_id).select { |k,v| v.size > 1}
if duplicate_items.keys.empty?
return
elsif duplicate_items.select{|k,v| v.group_by(&:date_of_lease).count != v.count}.empty?
# There are no duplicate object ids that also have duplicate
# dates of lease between themselves
else
errors.add('It is not possible to purchase many times one item with the same values')
end
end
Check that you have to handle the case where there are different object ids with the same date of lease in the same items array that has duplicates, that should be valid. For example : Item id 1, date 12, Item id 1, date 13, item id 2, date 12 Should be valid.

Rails: why does activerecord find and find_by_attribute don't behave the same?

I love activerecords multiple find:
Country.find(1, 2) # Returns an array of countries
I love auto find_by_attribute generated:
Country.find_by_iso2('US') # Equivalent to Country.where(iso2: 'US').first
So why the combination doesn't work/exists?
Country.find_by_iso2('US', 'CA')
# Would expect an array, it fails because too many arguments
Country.find_by_iso2(['US', 'CA'])
# Would expect an array, returns only the last one (Canada)
Instead I sadly have to write:
['US', 'CA'].map{ |e| Country.find_by_iso2(e) }
which is much less elegant.
Model.find_by(*args)
Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
If no record is found, returns nil.
Post.find_by name: 'Spartacus', rating: 4
Post.find_by "published_at < ?", 2.weeks.ago
docs: http://apidock.com/rails/v4.0.2/ActiveRecord/FinderMethods/find_by
File activerecord/lib/active_record/relation/finder_methods.rb, line 47
def find_by(*args)
where(*args).take
end
Beacuse it accepts an arguments as array and return only first element of it

Scoped and scope in rails

Can somebody explain what this method does and what I can pass to it?
scoped(options = nil)
Returns an anonymous scope.
And also what the scope method does? I don't understand after reading the documentation.
In ActiveRecord, all query building methods (like where, order, joins, limit and so forth) return a so called scope. Only when you call a kicker method like all or first the built-up query is executed and the results from the database are returned.
The scoped class method also returns a scope. The scope returned is by default empty meaning the result set would not be restricted in any way meaning all records would be returned if the query was executed.
You can use it to provide an "empty" alternative like in the query_by_date example by MurifoX.
Or you can use it to combine multiple conditions into one method call, like for example:
Model.scoped(:conditions => 'id < 100', :limit => 10, :order => 'title ASC')
# which would be equivalent to
Model.where('id < 100').limit(10).order('title ASC')
The scope class method allows you to define a class method that also returns a scope, like for example:
class Model
scope :colored, lambda {|col|
where(:color => col)
}
end
which can be used like this:
Model.colored
The nice thing with scopes is that you can combine them (almost) as you wish, so the following is absolutely possible:
Model.red.where('id < 100').order('title ASC').scoped(:limit => 10)
I also strongly suggest reading through http://guides.rubyonrails.org/active_record_querying.html
I have used it in the past.When you make chained calls to the ActiveRecord query interface like this:
Model.where(:conditions).where(:more_conditions).where(:final_conditions)
Each one of them is already scoped, making the chain work without any problems. But let's say you have something like this:
Model.query_by_date(date).query_by_user(user).query_by_status(status)
scope :query_by_date, lambda { |date|
case date
when "today"
where(:date => Date.today)
when "tomorrow"
where(:date => Date.tomorrow)
else
# Any value like '' or 0 or Date.whatever
end
}
This would cause an error if the date param is not today or tomorrow. It would pick the last value and try to chain this query with the next one query_by_user, resulting in a undefined method default_scoped? for ''. But if you put a scoped method in the else condition, it would work without any flaws, because you are saying to activerecord that you pass through this method/named scope and didn't make any calls to where/find/other activerecord methods, but returned a scoped object, so you can continue chaining queries and stuff.
It would be this way in the end.
else
scoped
end
Hope you understand this simple example.

Resources