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.
Related
I have an order that contains multiple bookings and I am trying to apply select condition on the bookings that specify certain condition specified in the hash in the following way
{ status: 'confirmed',
slot: '1 p.m. - 2 p.m.'}
I need to make a generic function that works on bookings for any number of filters that satisfy the conditions.In this case the condition is {slot '1 p.m. - 2 p.m.' and status is confirmed}
I made the following function for this but was not able to give the && conditions from filters
def bookings_of(filters)
self.bookings.select do |booking|
filters.map{ |key, value| booking.send(key) == value }
end
end
How should I do this?
If you really wanted to do this in Ruby you could do:
def bookings_of(filters)
self.bookings.select do |booking|
!!filters.map { |key, value| break if booking.send(key) != value }
end
end
Using break here will bail as soon as one of the filters does not pass and it uses the fact that nil is falsy while an array is truthy.
But you should probally just use .where and do it in the database instead.
In which case you can do it in one call by merging the array of hashes into a single hash:
bookings.where(filters.reduce({}, :merge))
While you may be able to apply all your filters by using send on individual objects, you would probably benefit from letting your database do the work by using ActiveRecord's where.
Beware that If these filters come from request parameters you may be opening a security hole in your application by using send or where.
Consider using an allowlist approach, where you build up the final query depending on all the given filters:
def bookings_of(slot:, status:)
query = bookings
query = query.where(slot: slot) if slot
if ["confirmed", "abandonned"].include?(status)
query = query.where(status: status)
end
query
end
def bookings_of(filters)
self.bookings.select do |booking|
filters.map{ |key, value| booking.send(key) == value }.inject(:and)
end
end
I'm making tickets for a small (<150 person) event and would like to auto increment ticket numbers and save those numbers to the database. Do I use a "hidden_field"? My database is set up with ticket.number as an array, because a person may buy several tickets. So what's the proper syntax? Thanks!
Is your database PostgreSQL? That supports storing arrays natively, so you can do
"select max(select max(x) from unnest(ticket_array) x) from people"
I haven't tested it so I'm not positive about the phrasing but it's something like that.
However your database is small enough that you can do it in Rails which should work for any type of database if you're storing the array as a serialised string.
last_number = Person.all.map{|person| person.ticket_array.max }.max
You'd use this in a before save, and I assume you have an integer column number_of_tickets, so you could do...
class Person
serialize :ticket_array
before_save :determine_ticket_numbers
def determine_ticket_numbers
return if persisted?
last_number = self.class.all.map{|person| person.ticket_array.max }.max
last_number ||= 0
number_of_tickets.times { self.ticket_array << (last_number += 1) }
end
end
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
I have some code that is chugging through a set of Rails Active Record models, and setting an attribute based on a related value from a 2D Array.
I am essentially setting a US State abbreviation code in a table of US States which was previously only storing the full names. A library of state names is being used to derive the abbreviations, and it contains a 2D Array with each sub-array having a full name, and an abbreviation (i.e., [['New York', 'NY']['Pennsylvania', 'PA'][etc]]). I compare the state name from each record in the database to each full text name in this Array, then grab the corresponding sibling Array cell when there is a match.
This code works fine, and produces the correct results, but its frumpy looking and not easily understood without reading many lines:
# For the following code, StatesWithNames is an Active Record model, which is
# having a new column :code added to its table.
# Sates::USA represents a 2D Array as: [['StateName', 'NY']], and is used to
# populate the codes for StatesWithNames.
# A comparison is made between StatesWithNames.name and the text name found in
# States::USA, and if there is a match, the abbreviation from States::USA is
# used
if StatesWithNames.any?
StatesWithNames.all.each do |named_state|
if named_state.code.blank?
States::USA.each do |s|
if s[0] == named_state.name
named_state.update_column(:code, s[1])
break
end
end
end
end
end
What is the most Ruby style way of expressing assignments with logic like this? I experimented with a few different procs / blocks, but arrived at even cludgier expressions, or incorrect results. Is there a more simple way to express this in fewer lines and/or if-end conditionals?
Yea, there is a few ifs and checks, that are not needed.
Since it is Rails even though it does not state so in question's tags, you might want to use find_each, which is one of the most efficient way to iterate over a AR collection:
StatesWithNames.find_each do |named_state|
next unless named_state.code.blank?
States::USA.each do |s|
named_state.update_column(:code, s[1]) if s[0] == named_state.name
end
end
Also be aware, that update_column bypasses any validations, and if you wish to keep your objects valid, stick to update!.
And last thing - wrap it all in transaction, so if anything goes wrong all the way - it would rollback any changes.
StatesWithNames.transaction do
StatesWithNames.find_each do |named_state|
next unless named_state.code.blank?
States::USA.each do |s|
named_state.update!(:code, s[1]) if s[0] == named_state.name
end
end
end
You might use a different data structure for this.
With your existing 2D array, you can call to_h on it to get a Hash where
a = [['California', 'CA'], ['Oregon', 'OR']].to_h
=> { 'California' => 'CA', 'Oregon' => 'OR' }
Then in your code you can do
state_hash = States::USA.to_h
if StatesWithNames.any?
StatesWithNames.all.each do |named_state|
if named_state.code.blank?
abbreviation = state_hash[named_state.name]
if !abbreviation.nil?
named_state.update_column(:code, abbreviation)
end
end
end
end
the first thing you want to do is convert the lookup from an array of arrays to a hash.
state_hash = States::USA.to_h
if StatesWithNames.any?
StatesWithNames.all.select{|state| state.code.blank?}.each do |named_state|
named_state.update_column(:code, state_hash[named_state.name]) if state_hash[named_state.name]
end
end
I'm trying to implement my first ruby sorting algorithm. This algorithm is based on some specific rules ("always prefer objects of type xxx over objects of types yyy"), and if none of these rules triggered, it uses the ruby <=>-operator. I'm doing this on a ruby-on-rails one-to-many association.
The problem is this algortihm does not return the array itself, it just returns -1 or 1, the result of the comparison..But I actually don't understand why, as my result is only returned in the sort-block.
Here is my current code:
def sort_products!
products.sort! do |p1, p2|
result = 0
# Scalable Products are always the last ones in order
if p1.class.name == "ScalableProduct"
result = -1
elsif p2.class.name == "ScalableProduct"
result = 1
end
if result == 0
# Put products producing electricity and heating down
if p1.can_deliver_electricity?
result = -1
elsif p2.can_deliver_electricity?
result = 1
end
end
# Else: Just compare names
result = p1.name <=> p2.name if result == 0
result
end
end
The best practice here, in my opinion, would be to implement the <=> inside the Product model. You'll need to include the Comparable model in order to achive this:
class Product
include Comparable
def <=>(another_product)
# Compare self with another_product
# Return -1, 0, or 1
end
end
Then your sorting method will be reduced to:
def sort_products!
products.sort!
end
Change the do..end for brackets as delimiters of the block. It is first sorting, and then using the block on the result (because of the precedence of the do..end syntax). Using brackets, it uses the block as a sorting block, which is what you wanted.
Also, in your comparison, if both your products are ScalableProduct then you will not order them in a sensible way. If they are both ScalableProduct at the same time, you might want to keep result as 0 so it falls back to comparing by name. Same deal with can_deliver_electricity?.