Ruby shorthand for "use this if it isn't blank, otherwise use that" - ruby-on-rails

I have the following code:
url = file.s3_url.blank? ? file.url : file.s3_url
Is there a shorter way to write this?
Thanks!

There is an abstraction for that in ActiveSupport, Object#presence:
url = file.s3_url.presence || file.url

Well, you could write a method on whatever file is an instance of (say S3File):
class S3File
def real_url
self.s3_url.blank? ? self.url : self.s3_url
end
#...
end
Then it gets real simple:
url = file.real_url
As #tokland said, you could monkey patch Object to use an or_if method, which would be implemented like this:
class Object
def or_if(method, val = nil)
self.send(method) ? (block_given? ? yield : val) : self
end
end
This way, you'd be able to do this:
url = file.s3_url.or_if(:blank?) { file.url }
Or this:
url = file.s3_url.or_if(:blank?, file.url)

Maybe, you can do the following:
url = file.s3_url || file.url
This code will only use file.url if file.s3_url is nil. That means that an empty string won't work though. If you want to ensure that an empty string is not used, like you do in your example, then there isn't a shorter way to do this.

I use a utility method.
def until_not_blank(*args)
args.find {|a| !a.blank? }
end
url = until_not_blank(file.s3_url, file.url)
I typically just put that in my ApplicationController and make it a helper. If you wanted it to be available globally, you could put it in Kernel, or you could monkey-patch Array
class Array
def first_not_blank
find {|a| !a.blank?}
end
end
url = [file.s3_url, file.url].first_not_blank

url = (file.s3_url.blank? && file.s3_url) || file.url
Looks cool but I actually find it confusing and prefer good old ternary if.

Related

Ruby: strip iframe and convert its src to a var

I'm trying to parse a string that has an iframe in it, convert its src attribute to a specially-formatted Ruby variable, then replace the iframe in the string with the Ruby variable formatted in a particular way. So far I've written this:
def video_parse(string)
if string.include?('youtube.com/?v=')
url = 'youtube.com/?v='
string.gsub!('<iframe>.*</iframe>', video_service('YOUTUBE', vid(string, url)))
end
if string.include?('player.vimeo.com/video/')
url = 'player.vimeo.com/video/'
string.gsub!('<iframe>.*</iframe>', video_service('VIMEO', vid(string, url)))
end
string
end
def vid(string, url)
string.split(url).last.split(/['"]/).first
end
def video_service(service, vid)
"*|#{service}:[$vid=#{vid}]|*"
end
But it doesn't replace anything. I suspect my wildcard iframe tag selection is wrong, plus my vid method is a little clunky. How can I get my wildcard in gsub to work correctly? And for bonus points, can I write it a little more efficiently so I'm not parsing string to reformat the src in iframe?
Update
String looks something like this:
string = 'resources rather than creating our luck through innovation.\n<br>\n<br> \n<iframe allowfullscreen=\"\" frameborder=\"0\" height=\"311\" mozallowfullscreen=\"\" name=\"vimeo\" src=\"http://player.vimeo.com/video/222234444\" webkitallowfullscreen=\"\" width=\"550\"></iframe>\n<br>\n<br>\nThat hasn’t stoppe'
Second attempt looks like this, still doesn't replace anything:
def mailchimp_video_parse(string)
if string.include?('youtube.com/?v=')
string.gsub!(iframe) { video_service('YOUTUBE', vid(Regexp.last_match[1])) }
end
if string.include?('player.vimeo.com/video/')
string.gsub!(iframe) { video_service('VIMEO', vid(Regexp.last_match[1])) }
end
string
end
def vid(iframe)
iframe.split!('src').last.split!(/"/).first
end
def iframe
'<iframe.*<\/iframe>'
end
def video_service(service, vid)
"*|#{service}:[$vid=#{vid}]|*"
end
Still nothing.
A bit safer with Nokogiri:
d = Nokogiri::HTML(string)
d.css('iframe').each do |i|
if i['src'] =~ %r{(youtube|vimeo).*?([^/]+)$}i
i.replace(video_service($1.upcase, $2)
end
end
puts d.to_html
(But note that it is less efficient than the pure regexp solution, as Nokogiri will parse the whole HTML.)
The iframe method should be /<iframe.*<\/iframe>/ for it to be properly be recognized as a regex
The Regexp.last_match[1] should be Regexp.last_match[0] in the mailchimp_video_parse method
split! needs to be just split in the vid method (there is no split! method in Ruby)
Edited methods:
def mailchimp_video_parse(string)
if string.include?('youtube.com/?v=')
string.gsub!(iframe) { video_service('YOUTUBE', vid(Regexp.last_match[0])) }
end
if string.include?('player.vimeo.com/video/')
string.gsub!(iframe) { video_service('VIMEO', vid(Regexp.last_match[0])) }
end
string
end
def vid(iframe)
iframe.split('src').last.split(/"/).first
end
def iframe
/<iframe.*<\/iframe>/
end

Spree error when using decorator with the original code

Need a little help over here :-)
I'm trying to extend the Order class using a decorator, but I get an error back, even when I use the exactly same code from source. For example:
order_decorator.rb (the method is exactly like the source, I'm just using a decorator)
Spree::Order.class_eval do
def update_from_params(params, permitted_params, request_env = {})
success = false
#updating_params = params
run_callbacks :updating_from_params do
attributes = #updating_params[:order] ? #updating_params[:order].permit(permitted_params).delete_if { |k,v| v.nil? } : {}
# Set existing card after setting permitted parameters because
# rails would slice parameters containg ruby objects, apparently
existing_card_id = #updating_params[:order] ? #updating_params[:order][:existing_card] : nil
if existing_card_id.present?
credit_card = CreditCard.find existing_card_id
if credit_card.user_id != self.user_id || credit_card.user_id.blank?
raise Core::GatewayError.new Spree.t(:invalid_credit_card)
end
credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
attributes[:payments_attributes].first[:source] = credit_card
attributes[:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
attributes[:payments_attributes].first.delete :source_attributes
end
if attributes[:payments_attributes]
attributes[:payments_attributes].first[:request_env] = request_env
end
success = self.update_attributes(attributes)
set_shipments_cost if self.shipments.any?
end
#updating_params = nil
success
end
end
When I run this code, spree never finds #updating_params[:order][:existing_card], even when I select an existing card. Because of that, I can never complete the transaction using a pre-existent card and bogus gateway(gives me empty blanks errors instead).
I tried to bind the method in order_decorator.rb using pry and noticed that the [:existing_card] is actuality at #updating_params' level and not at #updating_params[:order]'s level.
When I delete the decorator, the original code just works fine.
Could somebody explain to me what is wrong with my code?
Thanks,
The method you want to redefine is not really the method of the Order class. It is the method that are mixed by Checkout module within the Order class.
You can see it here: https://github.com/spree/spree/blob/master/core/app/models/spree/order/checkout.rb
Try to do what you want this way:
Create file app/models/spree/order/checkout.rb with code
Spree::Order::Checkout.class_eval do
def self.included(klass)
super
klass.class_eval do
def update_from_params(params, permitted_params, request_env = {})
...
...
...
end
end
end
end

Rails personal class Each routine

I'm kind of a newbie in some areas of ruby and rails. So I I'm writing a class to read excel depending on the extension and return the row in a each routine. Something like this:
class ExcelRead
(dependencies)
def initialize(path, sheet_n = 0)
type = File.extname(path)
if type == JitExcelRead::XLS
Spreadsheet.client_encoding = 'UTF-8'
book = Spreadsheet.open path
book_sheet = book.worksheet sheet_n
elsif type == JitExcelRead::XLSX
book = Creek::Book.new path
book_sheet = book.sheets[sheet_n]
end
#book = book
#book_sheet = book_sheet
#book_rows = book_sheet.rows
#path = path
#type = type
end
end
So this means that I call on my application
xls = ExcelRead.new(uploaded_file.filename_path)
and everything runs smooth. I have the objects I need at my disposal. My problem now is how to iterate through them. I thought that adding a method to may class like this
def each
binding.pry
end
and calling it normally on my app like so
xls.book_rows.each do |row|
end
would make me enter that code, but not really...
help?
If you added a each method to your ExcelRead class, and you create an instance of this class called xls, then you have to access it using xls.each, not xls.book_rows.each.
Using the former, you are calling the each method from the Enumerator, as book_rows is a collection.
I can only guess that you want a custom way to iterate your book_row, so i think something like this should be what you are trying to achieve:
def iterate
self.book_rows.each do |br|
# do stuff
end
end
And you call it like:
xls.iterate
But this is only a wild guess.

Writing a method that simply multiplies the object by something

I'm trying to convert a bunch of numbers from imperial to metric on the front end of my site depending on if the user has set their measurement_units to 'metric' or 'imperial'
I can just do #myWeight*.45 to convert the number, but what I want to do is write a helper method like this
def is_imperial?
if User.measurement_units == 'metric'
*0.453592
elsif User.measurement_units == 'imperial'
*1
end
end
then be able to do this: #myWeight*.is_imperial?
I'm just not sure how I would assign the *value to the method is_imperial?
Thanks for the help!
EDIT:
#myWeight is a float calculated from adding several numbers.
I'm just trying to find an elegant way of converting any number that shows up on the site to metric if the user has metric as the value in the measurement_units field on the User model.
I assumed I would need to create a helper method in the application_helper.rb. Is that not correct?
I think you mean something like this:
class User
def imperial
f_multiplier = 0.0
f_multiplier = !!(self.measurement_units == 'metric') ? 0.453592 : 1
imperial = self.weight * f_multiplier
end
end
puts #myWeight.imperial
If you want the measurement_units method to be dynamic based on the user, then I think you need to make it an instance method.
Modify the is_imperial? method to return the right number:
def is_imperial?
if measurement_units == 'metric'
0.453592
elsif measurement_units == 'imperial'
1
end
end
Then you can call the method with something like this:
#myWeight.send(:*, is_imperial?)
If #myWeight represents a User object you might have to change it to this:
#myWeight.weight.send(:*, is_imperial?)
Methods that end with a ? in Ruby are expected to return true or false, so you should rename the method to be something like weight_conversion_factor.
Assuming #myWeight is Float value, you seem like you are looking for how to monkey patch. Check in rails console, #myWeight.class.name returns Float.
For monkey patching in Rails,
Create config/initializers/extensions directory. This is where you will store any future monkey patched methods.
Create a file called, floats.rb.
Add the following code.
class Float
def is_imperial?
if User.measurement_units == 'metric'
self*0.453592
elsif User.measurement_units == 'imperial'
self*1
end
end
end
Make sure to restart the Rails server to reinitialize.

Ruby on Rails: how do I set a variable where the variable being changed can change?

i want to do
current_user.allow_????? = true
where ????? could be whatever I wanted it to be
I've seen it done before.. just don't remember where, or what the thing is called.
foo = "bar"
current_user.send("allow_#{foo}=", true)
EDIT:
what you're asking for in the comment is another thing. If you want to grab a constant, you should use for instance
role = "admin"
User.const_get(role)
That's a "magic method" and you implement the method_missing on your current_user object. Example from Design Patterns
#example method passed into computer builder class
builder.add_dvd_and_harddisk
#or
builder.add_turbo_and_dvd_dvd_and_harddisk
def method_missing(name, *args)
words = name.to_s.split("_")
return super(name, *args) unless words.shift == 'add'
words.each do |word|
#next is same as continue in for loop in C#
next if word == 'and'
#each of the following method calls are a part of the builder class
add_cd if word == 'cd'
add_dvd if word == 'dvd'
add_hard_disk(100000) if word == 'harddisk'
turbo if word == 'turbo'
end
end

Resources