Using a specific method inside a scope query rails - ruby-on-rails

I have in database something called device
And I want to extract from the database devices that Semantic Version is greater/equal than '1.2.42' and if the device status is online
I have a method
def check_sem_ver
basic_sem_version = "1.2.42"
device_base_ver = Device.harware_info["software-version"].split("-").first
return Gem::Version.new(device_base_ver) >= Gem::Version.new(basic_sem_version)
end
As well as I have a scope called
scope :proper_sem_version, -> { where(online-status: 'online).and(check_sem_ver) }
But this does not work, how can I extract something from the database only if check_sem_ver is true?
Unfortunately basic maths inside where like where(software-version > '1.2.42') does not work because version is a string with - as you can see that I have to split to get rid of.
Any ideas?

For the expected comparison of version strings, they need to be converted into arrays.
If you're using Postgres, you can structure a scope like this:
BasicSemVersion = "1.2.42"
def query_threshold
BasicSemVersion.split('.').join(',')
end
scope :proper_sem_version, -> { where("online-status='online' and string_to_array(software-version,'.')::int[]>=ARRAY[#{query_threshold}]") }

Related

cyclomatic complexity is too high rubocop for method

This is code I have using in my project.
Please suggest some optimizations (I have refactored this code a lot but I can't think of any progress further to optimize it )
def convert_uuid_to_emails(user_payload)
return unless (user_payload[:target] == 'ticket' or user_payload[:target] == 'change')
action_data = user_payload[:actions]
action_data.each do |data|
is_add_project = data[:name] == 'add_fr_project'
is_task = data[:name] == 'add_fr_task'
next unless (is_add_project or is_task)
has_reporter_uuid = is_task && Va::Action::USER_TYPES.exclude?(data[:reporter_uuid])
user_uuids = data[:user_uuids] || []
user_uuids << data[:owner_uuid] if Va::Action::USER_TYPES.exclude?(data[:owner_uuid])
user_uuids << data[:reporter_uuid] if has_reporter_uuid
users_data = current_account.authorizations.includes(:user).where(uid: user_uuids).each_with_object({}) { |a, o| o[a.uid] = {uuid: a.uid, user_id: a.user.id, user_name: a.user.name} }
if Va::Action::USER_TYPES.include? data[:owner_uuid]
data['owner_details'] = {}
else
data['owner_details'] = users_data[data[:owner_uuid]]
users_data.delete(data[:owner_uuid])
end
data['reporter_details'] = has_reporter_uuid ? users_data[data[:reporter_uuid]] : {}
data['user_details'] = users_data.values
end
end
Note that Rubocop is complaining that your code is too hard to understand, not that it won't work correctly. The method is called convert_uuid_to_emails, but it doesn't just do that:
validates payload is one of two types
filters the items in the payload by two other types
determines the presence of various user roles in the input
shove all the found user UUIDs into an array
convert the UUIDs into users by looking them up
find them again in the array to enrich the various types of user details in the payload
This comes down to a big violation of the SRP (single responsibility principle), not to mention that it is a method that might surprise the caller with its unexpected list of side effects.
Obviously, all of these steps still need to be done, just not all in the same method.
Consider breaking these steps out into separate methods that you can compose into an enrich_payload_data method that works at a higher level of abstraction, keeping the details of how each part works local to each method. I would probably create a method that takes a UUID and converts it to a user, which can be called each time you need to look up a UUID to get the user details, as this doesn't appear to be role-specific.
The booleans is_task, is_add_project, and has_reporter_uuid are just intermediate variables that clutter up the code, and you probably won't need them if you break it down into smaller methods.

Validations Custom - Compare two variables between new and existing objects (dates) Rails 5

My rails application is a residence application.
I have a model stay which is a third model in a has_many:through assocation between a tenant and a flat. A stay is defined by a checkin_date and a checkout_date. I would like to create a custom validations to prevent the creation of a new stay if there is already a tenant in a flat for a given period...but i never write a custom validation before so i'm super lost...
By logic, i know i need to compare the checkin_date and checkout_date of the "already existing record" and the "supposed new one". So I guess it would look like : (n+1= the new record vs. n= already existing record)
> def duplicate_stay
>
> if Stay.exists?(tenant_id: current_tenant.id, studio_id:
> current_studio.id) &&
> checkin_date(n+1) > checkin_date(n)
> checkout_date(n+1) > checkin_date(n)
> checkin_date(n+1) < checkout_date(n)
> checkout_date(n+1) < checkout_date(n)
> == false
> else
> == true (the model can be created)
end
Can someone help me? I keep looking but I know understand how to do it !
Try using the cover? method. You can use it like this:
today = Date.today
in_one_week = today + 7.days
search_date = today + 3.days
(today..in_one_week).cover? search_date
=> true
This is not tested, but if you added a scope to the Stay class:
scope :overlaps, ->(stay) {
where(
tenant_id: stay.tenant_id,
studio_id: stay.studio_id,
).
where(arel_table[:checkin_date].lt(stay.checkout_date)).
where(arel_table[:checkout_date].gt(stay.checkin_date))
}
... then for an instance of Stay #stay calling Stay.overlaps(#stay).exists? would return true if there was any instance that overlapped with #stay.
Let me know if that doesn't work. It's often helpful to look at the SQL that is executed as part of a validation if you're not sure why something isn't working the way you think it should.
Edit: one tricky thing here is that a stay overlaps with itself, so if #stay was saved successfully (ie. it has an id assigned) you might have a problem with that logic. You can provide different logic depending on whether the record is saved or not, though.
I think that this would do it:
scope :overlaps, ->(stay) {
logic = where(
tenant_id: stay.tenant_id,
studio_id: stay.studio_id,
).
where(arel_table[:checkin_date].lt(stay.checkout_date)).
where(arel_table[:checkout_date].gt(stay.checkin_date))
logic = logic.where.not(id: stay.id) unless stay.new_record?
logic
}

How to update multiple instances efficiently in Rails

I have a Company model that have lending_restricted:boolean column.
The list about the restriction are collected by restricted_codes method.
And to update only necessary companies, I wrote like this:
old_codes = Company.where(lending_restricted: true).pluck(:code)
new_codes = restricted_codes
(new_codes - old_codes).each do |code|
c = Company.find_by(code: code)
c.try(:update_attributes, lending_restricted: true)
end
(old_codes - new_codes).each do |code|
c = Company.find_by(code: code)
c.try(:update_attributes, lending_restricted: false)
end
It works basically fine, but I feel it's a bit redundant to write similar function two times.
Is there better way to write a method like this?
The number of restricted_codes is about 100, and there are about 4000 companies in my Rails project.
Untested, but perhaps something like this? I've also updated your code so it's done in one query (instead of N queries).
def update_lending_restriction(codes, restriction)
Company.where(code: codes).update_all(lending_restricted: restriction)
end
old_codes = Company.where(lending_restricted: true).pluck(:code)
new_codes = restricted_codes
update_lending_restriction(new_codes - old_codes, true)
update_lending_restriction(old_codes - new_codes, false)

Ruby on Rails statement executed on an array check if subelement string is unique

I have an array objects, for this example lets call it Diff. These diffs have multiple fields that are not all the same (old_image, new_image, url, etc). new_image and old_image in this case have fields on them, most importantly a field called image_file_name.
I want to get an array of all the diffs with an unique old_image.image_file_name i.e. no diff should have an old_image with the same file name.
I believe the logic should look something like this.
unique_diffs = Array.new
#diff.build.diffs.each { |diff|
if diff.old_image.image_file_name != #diff.old_image.image_file_name
unique_diffs.push(diff)
end
}
Or something like this
#unique_diffs = #diff.build.diffs.map{|diff| diff.old_image.image_file_name}.uniq
Any help would be much appreciated.
Try something like this:
Diff = Struct.new(:old_image)
Image = Struct.new(:image_file_name)
diffs = [
Diff.new(Image.new('name1')),
Diff.new(Image.new('name2')),
Diff.new(nil),
Diff.new(Image.new('name1')),
]
uniqs = diffs.select { |diff| diff.old_image }.uniq { |diff| diff.old_image.image_file_name }
p uniqs # prints Diff with name1 and Diff with name 2
The only important line is the one that calls select and uniq.
You need to use select to leave only the diffs with the old image, and then use uniq to drop those with the duplicated image file names.
I ended up using the loop, I was hoping to make this cleaner with the uniq function but it didn't seem to work, it gave me back all the diffs instead of the ones with the unique old image filename.
#diff.build.diffs.each { |diff|
if diff.old_image.image_file_name == #diff.old_image.image_file_name
# Logic went here
end
}
Still open to improving this but for now this will have to do.

how to use ilike with Integer in grails

I use EasyGrid plugin and must find values where integer field like '%001%'
initialCriteria {
ilike('id', "%"+params.id+"%")
}
But ilike doesn't work with Integer. How to do it?
I tried to do:
initialCriteria {
ilike('id'.toString(), "%"+params.id+"%")
}
initialCriteria {
ilike('str(id)', "%"+params.id+"%")
}
but it's not work.
If id is an integer in the database, then ilike doesn't really make much sense and there is probably a better way to do what you are trying to do (like adding a type field or something to the domain object, and filter by type)
However, you should be able to do something like this (untested):
initialCriteria {
sqlRestriction "cast( id AS char( 256 ) ) like '%001%'"
}
Following criteria not working if you search from your textbox when user search any text character by mistake like 12dfdsf as your searchable id. It will give you an exception
initialCriteria {
ilike('id', "%"+params.id+"%")
}
For better use you can use following criteria
initialCriteria {
sqlRestriction "id like '%${params?.id}%'"
}
You could do:
String paddedId = params.id.toString().padLeft(3,'0')
initialCriteria {
ilike('id', "%$paddedId%")
}
The solution offered by tim_yates with the sqlRestriction would work in version 1.5.0 of easygrid.
One of the main differences from 1.4.x is that the gorm datasource no longer uses DetachedCriteria, but Criteria - which maps directly to Hibernate's Criteria API.
So you can try it on the last version.
(Keep in mind that the upgrade might break your existing grids. There's also many other changes)
Another small observation is that 'initialCriteria' is not the right place to do stuff like that. (it's not wrong, but there is a 'globalFilterClosure' property for applying column independent filters)
I mixed the code posted by #tim_yates and mine:
String paddedId = params.id.toString().padLeft(3,'0')
def crit = Book.withCriteria {
sqlRestriction "lpad(cast( id AS char( 256 ) ), 3, '0') like '%${paddedId}%'"
}
I've tried with a h2 in-memory db and it works, but I am not sure about two things:
the real usefulness of that
lpad syntax consistence across all db engines
YMMV

Resources