Hey guys i'm new to RoR and ruby in general.
In this project i am working at the moment i'm showing 4 images of a vehicle that were previously uploaded by users.
In my class 'Vehicle' i have a singleton method - that returns an array with urls for attached images of a vehicle object - which i call like this Vehicle.vehicle_images(#vehicle.id). I'd like to make this method look more performatic and less hard-coded.
my vehicle class:
class Vehicle < ApplicationRecord
has_many :vehicles_attachment, dependent: :destroy
has_many :vehicles_attachment_imagem, -> { where({ type_attachment: 'imagem' }) }, class_name: 'VehiclesAttachment', dependent: :destroy
def self.vehicle_images(vehicle_id)
if vehicle_id
images = VehiclesAttachment.where({ vehicle_id: vehicle_id, type_attachment: :imagem })
urls = []
if !images.nil? && !images.empty?
count = 0
images.each do |img|
urls << if img.attachment.file?
"https:#{img.attachment.url(:regular)}"
else
4.times do
urls << ActionController::Base.helpers.image_url('viatura.jpg', digest: false).to_s
end
end
count += 1
end
if count < 4
(4 - count).times do
urls << ActionController::Base.helpers.image_url('viatura.jpg', digest: false).to_s
end
end
else
4.times do
urls << ActionController::Base.helpers.image_url('viatura.jpg', digest: false).to_s
end
end
else
4.times do
urls << ActionController::Base.helpers.image_url('viatura.jpg', digest: false).to_s
end
end
urls
end
end
Can you help me?
As a first step it could be simplified to this:
def self.vehicle_images(vehicle_id)
blank_image_url = ActionController::Base.helpers.image_url('viatura.jpg', digest: false).to_s
urls = []
if vehicle_id
VehiclesAttachment.where(vehicle_id: vehicle_id, type_attachment: :imagem).each do |image|
urls << img.attachment.file? ? "https:#{img.attachment.url(:regular)}" : blank_image_url
end
end
urls << blank_image_url while urls.size < 4
urls
end
But this is just a first step. The next steps could be:
Determine if you have to deal with blank vehicle_id. If not then an instance method on a vehicle would be better than a class method. And you would be able to use the defined association instead of the where on the class.
I would investigate if it is really possible that you sometimes get only some images returned and really have to fill in the dummy image between them. Or if it would be enough to only fill in dummy images at the end if there are less than 4. That would simplify the method further.
I wonder if you sometimes have to deal with more than 4 images? Your code currently doesn't handle that.
Perhaps you could add variations to ensure that there are always exactly the four images?
Related
In my app that I am building to learn Rails and Ruby, I have below iteration/loop which is not functioning as it should.
What am I trying to achieve?
I am trying to find the business partner (within only the active once (uses a scope)) where the value of the field business_partner.bank_account is contained in the field self_extracted_data and then set the business partner found as self.sender (self here is a Document).
So once a match is found, I want to end the loop. A case exists where no match is found and sender = nil so a user needs to set it manually.
What happens now, is that on which ever record of the object I save (it is called as a callback before_save), it uses the last identified business partner as sender and the method does not execute again.
Current code:
def set_sender
BusinessPartner.active.where.not(id: self.receiver_id).each do |business_partner|
bp_bank_account = business_partner.bank_account.gsub(/\s+/, '')
rgx = /(?<!\w)(#{Regexp.escape(bp_bank_account)})?(?!\w)/
if self.extracted_data.gsub(/\s+/, '') =~ rgx
self.sender = business_partner
else
self.sender = nil
end
end
end
Thanks for helping me understand how to do this kind of case.
p.s. have the pickaxe book here yet this is so much that some help / guidance would be great. The regex works.
Using feedback from #moveson, this code works:
def match_with_extracted_data?(rgx_to_match)
extracted_data.gsub(/\s+/, '') =~ rgx_to_match
end
def set_sender
self.sender_id = matching_business_partner.try(:id) #unless self.sender.id.present? # Returns nil if no matching_business_partner exists
end
def matching_business_partner
BusinessPartner.active.excluding_receiver(receiver_id).find { |business_partner| sender_matches?(business_partner) }
end
def sender_matches?(business_partner)
rgx_registrations = /(#{Regexp.escape(business_partner.bank_account.gsub(/\s+/, ''))})|(#{Regexp.escape(business_partner.registration.gsub(/\s+/, ''))})|(#{Regexp.escape(business_partner.vat_id.gsub(/\s+/, ''))})/
match_with_extracted_data?(rgx_registrations)
end
In Ruby you generally want to avoid loops and #each and long, procedural methods in favor of Enumerable iterators like #map, #find, and #select, and short, descriptive methods that each do a single job. Without knowing more about your project I can't be sure exactly what will work, but I think you want something like this:
# /models/document.rb
class Document < ActiveRecord::Base
def set_sender
self.sender = matching_business_partner.try(:id) || BusinessPartner.active.default.id
end
def matching_business_partners
other_business_partners.select { |business_partner| account_matches?(business_partner) }
end
def matching_business_partner
matching_business_partners.first
end
def other_business_partners
BusinessPartner.excluding_receiver_id(receiver_id)
end
def account_matches?(business_partner)
rgx = /(?<!\w)(#{Regexp.escape(business_partner.stripped_bank_account)})?(?!\w)/
data_matches_bank_account?(rgx)
end
def data_matches_bank_account?(rgx)
extracted_data.gsub(/\s+/, '') =~ rgx
end
end
# /models/business_partner.rb
class BusinessPartner < ActiveRecord::Base
scope :excluding_receiver_id, -> (receiver_id) { where.not(id: receiver_id) }
def stripped_bank_account
bank_account.gsub(/\s+/, '')
end
end
Note that I am assigning an integer id, rather than an ActiveRecord object, to self.sender. I think that's what you want.
I didn't try to mess with the database relations here, but it does seem like Document could include a belongs_to :business_partner, which would give you the benefit of Rails methods to help you find one from the other.
EDIT: Added Document#matching_business_partners method and changed Document#set_sender method to return nil if no matching_business_partner exists.
EDIT: Added BusinessPartner.active.default.id as the return value if no matching_business_partner exists.
So I have a form that can submit multiple images at once. I also wrote a wrapper for the Imgur API in Ruby. My problem is that since this happens completely synchronously, it takes forever and times out for even 10 images. I'm wondering if there is a better way that would be able to handle more images.
All I can think of is asynchronously submitting forms with one image each, but I'm not sure if that's a good idea or if it will just hold up other requests.
class MissionsController < ApplicationController
def add_images
image_ids = params[:images].collect do |image|
Image.with_imgur(
title: "#{#mission.trip.name} - #{current_user.username}",
image: image,
album_id: #mission.album.imgur_id,
user_id: current_user.id,
trip_id: #mission.trip_id
).imgur_id
end
end
end
class Image < ActiveRecord::Base
def self.with_imgur(options)
trip_id = options.delete(:trip_id)
user_id = options.delete(:user_id)
image = Imgur::Image.create(options)
create(
imgur_id: image["id"],
link: image["link"],
trip_id: trip_id,
user_id: user_id
)
end
end
https://github.com/tomprats/toms-missions/blob/master/app/models/imgur.rb#L116
class Imgur::Image < Base
def self.create(options)
url = "https://api.imgur.com/3/image"
params = {
image: options[:image],
album: options[:album_id],
type: options[:type] || "file", # "file" || "base64" || "URL"
title: options[:title],
description: options[:description]
}
api_post(url, params).body["data"]
end
end
I'd take a look at Typhoeus https://github.com/typhoeus/typhoeus - I've used this in the past to handle uploads etc to Amazon Glacier. I found that it could be much faster - Typhoeus gives you a future object and will handle the upload of you in the background without blocking the application.
I hope this helps!
I'm have a few collections in a controller like so:
def show
#cave = Cave.includes(cubs: :mamabear).where(id: params[:id]).first
#cubs = #cave.cubs
#papabear = Papabear.first
#dens = Den.where(papabear_id: #papabear.id)
end
and now I'm trying to sort cubs by dens so I can display the cubs with
#dens.each do |d|
d.cubs
end
so I wrote the following:
def show
....
#dens.each do |den|
den.cubs = [] ## < --- Den does not have an association with Cub ##
#cubs.each do |cub|
den.cubs << cub if cub.mamabear.den_id == den.id
end
end
#reject dens if they don't have cubs
#dens = #dens.reject { |den| den.cubs.all?(&:blank?) }
end
But now I'm getting an undefined method 'cubs for Den' error because Den doesn't have an association with Cub. How do I assign an array of Cubs to each Den without an association?
1: I would create a standard association of Cubs for den. Probably a standard has_many On dens, so each cub has a den_id.
You're messing about re-inventing the wheel, otherwise.
2: you'll probably find that the undefined method is 'cubs=' as opposed to 'cubs'. It's an important distinction as it says what the code is doing when the error is thrown.
3: if you really want to ignore point 1 and make your own which fills an arbitrary attribute from the controller, you can add this to the Den model.
attr_accessor :cubs
Association is the best way to handle such scenarios if you want to fetch those cubs belonging to den at multiple places. if you dont want to implement association. you can try this solution
#den_cubs = []
#dens.each do |den|
cub_for_den= {} #here we are initializing hash each time for new den
#cubs.each do |cub|
cub_for_den[cub.id] = cub if cub.mamabear.den_id == den.id
end
#den_cubs << cub_for_den #put the hash in array
end
#den_cubs = #den_cubs.reject { |dc| dc.blank? }
on the show page you can do this
#den_cubs.each do |dc|
dc.each do |k,v|
# now here you can display all attributes for the cubs
end
end
Have you considered using a "has many through"-association in regards to cubs -> dens and dens -> cubs?
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Examples:
class Den
has_many :mamabears
has_many :cubs, through: :mamabears
end
class Cup
belongs_to :mamabear
has_one :den, through: :mamabear
end
Then you should be able to do something like:
den.cups #=> [<Cup ...>]
cup.den #=> <Den ...>
I have two models with the [fields]:
Order [:date]
Delivery Slot [:day]
Order belongs_to :delivery_slot
When an order is created, I want a delivery slot to be created with the :day set to the order :date.
So far I have created a new method create_delivery_slots in the Order controller that creates a Delivery Slot when the Order is created, but where I am stumped is, how do I get the Order :date in the Delivery Slot :day field?
#Create delivery slots if they dont already exist
def create_delivery_slots
existingslots = []
existingslots = DeliverySlot.all.select {|slot| slot.day == #order.date}
if existingslots.empty?
slot = DeliverySlot.new(:day => #order.date)
slot.save!
end
I have tried multiple approaches, but no luck. My gut tells me its something to do with strong parameters but I can't figure it out...
I'm not sure exactly of how you're set up but you'll probably want something like this:
class Order < ActiveRecord::Base
has_a :delivery_slot
after_create => :create_delivery_slots
.
#other code stuffs
.
.
private
def create_delivery_slots
existingslots = []
existingslots = DeliverySlot.all.select {|slot| slot.day == self.date}
if existingslots.empty?
slot = DeliverySlot.new(:day => self.date)
slot.save!
end
end
end
That's untested but it should be basically what you need.
I am writing a little plugin for my company redmine to assign unique documents [progressive] codes
the code I wrote so far works, but I don't think it is multi threads proof as there is a chance two users get the same document code.
I would like to find a way to lock the table while getting the last number and creating the new record with the incremented document number (D9999)
This is the active record:
class Documenti < ActiveRecord::Base
unloadable
def self.nextest
record=self.last
if (record.nil?) then ultimo=sprintf("D0000")
elsif (record.codice.nil?) then ultimo=sprintf("D0000")
else ultimo=record.codice
end
if (/^D[0-9]{4}/ =~ ultimo) == 0 then
c=ultimo.split("D")
p=c[1].to_i + 1
t=sprintf("D%04i",p)
end
return t
end
end
Controller then is like this:
def new
#documenti = Documenti.new
#documenti.codice=Documenti.nextest
respond_to do |format|
format.html # new.html.erb
end
end
so far the nextest value is not saved and another user can take the same value.