how to create dynamic variables in rails function? - ruby-on-rails

I have this code that's working:
case index
when "Books"
#reading_img = res.items.first.get_hash('MediumImage')["URL"] # don't show an image
#reading_link = create_amz_url(search, asin)
tempy = #nowreading.content.match(/#nowreading.*/).to_s.gsub("#nowreading",'') # strips away the #nowreading tag
#nowreading.content = tempy.match(/#{search}.*/).to_s.gsub("#{search}", #reading_link)
# replaces the book title (passed in as variable 'search') with the URL'ed title
when "Music"
#listening_img = res.items.first.get_hash('MediumImage')["URL"] # don't show an image
#listening_link = create_amz_url(search, asin)
tempy = #nowlistening.content.match(/#nowlistening.*/).to_s.gsub("#nowlistening",'') # strips away the #nowreading tag
#nowlistening.content = tempy.match(/#{search}.*/).to_s.gsub("#{search}", #listening_link)
# replaces the song title (passed in as variable 'search') with the URL'ed title
end
I need to repeat this for many, many categories. I tried something like this to DRY the code but it didn't work:
def get_img_and_links(act, res, search, asin)
'#'+act+'ing_img' = res.items.first.get_hash('MediumImage')["URL"] # don't show an image
'#'+act+'ing_link' = create_amz_url(search, asin)
tempy = '#now'+act+'ing'.content.match(/#now"#{act}"ing.*/).to_s.gsub("#now#{act}ing",'') # strips away the #nowreading tag
'#now'+act+'ing'.content = tempy.match(/#{search}.*/).to_s.gsub("#{search}", '#'+act+'ing_link')
# replaces the book title (passed in as variable 'search') with the URL'ed title
end
Essentially, I was trying to create a function that took an "act" (e.g., "read", "listen", etc) and have the variables within that function be dynamic. Can this be accomplished? If so, how?

Look up instance_variable_set here: http://ruby-doc.org/core/classes/Object.html. It's what you need to dynamically create these variables.
instance_variable_set "##{act}ing_img".to_sym, res.items.first.get_hash('MediumImage')["URL"]
And so on...

Good looking out trying to dry up your code. I would definitely use some hashes there instead of instance variables. Then you can use the key as the action. Just a thought.

IMPO, I think you should use more generic variables. Although the creating variables are supported by ruby, it will make your code hard to read
my suggestion is having some generic names like
#img (instead of reading_img, listing_img etc..)
#link (instead of reading_link, listing_link etc..)
and something like #content, because as your login at a given time only one will be selected and this wouldn't be a problem
Again, this is what i understood from the code you posted and correct me if I'm wrong
cheers
sameera

you should do something like this:
def setup
#reading_img = get_img(whatever)
#reading_link = get_link(whatever)
#listening_img = get_img(whatever)
#listening_link = get_link(whatever)
end
def get_img(whatever)
...do stuff and return stuff...
end
def get_link(whatever)
...do stuff and return stuff...
end

Related

rails - How to refactor this method

I got a little bit of a challenge but I don't know where to start. Long story short, I'm trying to make a method that will automatically translate a record from its model via Deepl or Google Translate.
I've got something working but I want to refactor it so it gets more versatile:
def translate
texts = [self.title_fr, self.desc_fr, self.descRequirements_fr, self.descTarget_fr, self.descMeet_fr, self.descAdditional_fr]
translations = DeepL.translate texts, 'FR', 'EN'
self.update(title_en: translations[0], descRequirements_en: translations[2], descTarget_en: translations[3], descMeet_en: translations[4], descAdditional_en: translations[5])
end
Hopefully this is self explanatory.
I would love to have a method/concern working like such :
def deeplTranslate(record, attributes)
// Code to figure out
end
and use it like such : deeplTranslate(post, ['title', 'desc', 'attribute3']). And that will translate the attributes and save the translated attributes to the database in en language.
Thanks in advance to anyone that can point me to a valid direction to go towards.
Okay, I actually managed to create an auto translate method for active record :
def deeplTranslate(record, attributes, originLang, newLang)
keys = attributes.map{|a| record.instance_eval(a + "_#{originLang}")}
translations = DeepL.translate keys, originLang, newLang
new_attributes = Hash.new
attributes.each_with_index do |a, i|
new_attributes[a + "_#{newLang}"] = translations[i].text
end
record.update(new_attributes)
end
Maybe it can get cleaner... But it's working : )

Optimize 'if else' conditions in rails

I am making an application, part of whose code requires many if .. else conditions:
if #model_name == "Style"
if row.include? ('colors')
colors = row['colors'].split(';')
model.style_colors.concat Color.where('code IN (?)', colors).map {|i| i.id.to_s }
row.delete('colors')
end
if row.include? ('gender') and row.include? ('garments')
#garments = row['garments']
#gender = row['gender']
row.delete('garments')
row.delete('gender')
end
if row.include? ('sports')
#sports = row['sports']
row.delete('sports')
end
if row.include?('decoration_packages')
#decorations_packages = row['decoration_packages']
row.delete('decoration_packages')
end
model.attributes = row.to_hash.merge!(active: FALSE)
else
model.attributes = row.to_hash
end
I need to make objects of row hash to access subclasses, and then delete them from row so it can be saved to a model.
Any idea how I can minimize the use of conditions or optimize it?
There's a few optimisations here...
row.include? ('gender') and row.include? ('garments')
could be implemented as
['gender', 'garments'].all?{|x| row.include?(x)}
#garments = row['garments']
row.delete('garments')
could be implemented as
#garments = row.delete('garments')
You could actually squash a lot of these onto one line:
if row.include? ('sports')
#sports = row['sports']
row.delete('sports')
end
could be
#sports = row.delete('sports') if row.include? ('sports')
Also worth considering:
Do you need to delete the values from 'row'? Could you just retrieve the value?
What are you trying to do here? It looks like you're pulling a hash into instance variables... Which is what ActiveRecord does, basically. Could you just create a model with these attributes and then call it in this style?
Style.new(row)
if #model_name == "Style"
if row.include?('colors')
model.style_colors.concat(
Color.where(code: row.delete('colors').split(';')).pluck(:id).map(&:to_s)
)
end
if row.include?('gender') and row.include?('garments')
#garments = row.delete('garments')
#gender = row.delete('gender')
end
if row.include?('sports')
#sports = row.delete('sports')
end
if row.include?('decoration_packages')
#decorations_packages = row.delete('decoration_packages')
end
model.attributes = row.to_hash.merge!(active: false)
else
model.attributes = row.to_hash
end
I would do something like this with your current code:
if #model_name == "Style"
row_key_set = row.keys.to_set
if row.include? 'colors'
colors = row['colors'].split(';')
color_ids = Color.where(code: colors).pluck(:id)
model.style_colors.concat(color_ids.map(&:to_s))
end
if row_key_set >= Set['gender', 'garments']
#garments = row.delete('garments')
#gender = row.delete('gender')
end
#sports = row.delete('sports')
#decorations_packages = row.delete('decoration_packages')
model.attributes = row.to_hash.merge(active: false)
else
model.attributes = row.to_hash
end
Instead of using Color.where('code IN (?)', colors) you can just use Color.where(code: colors).
Instead of using .map {|i| i.id.to_s } you can use pluck (.pluck(:id)) to get an array of color ids. This also makes for a quicker query since only the ids get fetched from the database instead of the whole records.
I personally like to use sets to check if multiple values are present in another set. For this reason I create the row_key_set variable row.keys.to_set. Now you can easily check certain keys are present on your hash by just checking if the key set is greater or equal than another set (thus being a superset). row_key_set >= Set['gender', 'garments'] With just one check you could leave this out, but if you have multiple checks this might be worth the trouble. I also find code written this way also more readable, but that's just personal peference.
You don't need to check if a key is present on a Hash, the documentation tells us the following:
Deletes the key-value pair and returns the value from hsh whose key is equal to key. If the key is not found, it returns nil.
This means you can leave out the include? check and write the result from the delete directly to the instance variable. If the key is not present nil will be set for the instance variable.
Lastly I would leave out the explanation mark in row.to_hash.merge!(active: false). The version without explanation mark doesn't alter the original array and reduces the chance on accidental side effects. You're saving the variable to model.attributes anyway and toss away the generated array from the to_hash method. It's normally better to use non-altering versions of methods, unless you explicitly want a certain effect to happen.

Intitalizing Object with Array of objects from another class Ruby

I have created a small Ruby class here:
class Star
#Star initialization
def initialize(star, number)
#star = star
#number = number
end
end
and I am looking to initialize a class called Solar System with 100 stars. This is what I have done and it doesn't seem to be working. Any help would be greatly appreciated.
require_relative 'star.rb'
class SolarSystem
#Initialize Game
def initialize(partOfSolarSystem)
#partOfSolarSystem = partOfSolarSystem
#stars_array = []
for i in 0..99
stars_array = Star.new('unknown_star',i)
end
end
def show_solar_system
#code here to show all the initialized stars in solar system
end
end
I can't seem to get it to initialize the array in the constructor. I would then like to be able to print out all of the elements in the stars array. Any help with this would be greatly appreciated.
Also in an effort to eventually move this to a database with rails or something of that nature, should I be looking to hash this or will this be easily converted to mySQL or another DB with some helper functions? I would eventually like to write this into rails and have a dynamic website for it.
Once again, thanks very much.
Your problem is assigning a new value to #stars_array variable on each iteration. There are multiple ways to deal with it:
#stars_array = (0..99).map { |i| Star.new('unknown_star',i) }
By the way, there is a couple of design issues (just for your attention):
Why variable is called stars_array, not just stars?
Why would ever instance of Star class have some object named #star inside? Recursion? :) Seems like #name would be proper and more clear attribute's name.
Don't miss indentation.
EDIT: About DB-mapping. Most common way - inherit both classes from ActiveRecord::Base, and create one-to-many relation from solar system to stars. Each class will have it's own table. Takes absolutely no efforts.
You are assigning the new object every time round the loop. The fix is to append the new object:
#stars_array << Star.new('unknown_star',i)
Or, if you prefer words rather than symbols:
#stars_array.push(Star.new('unknown_star',i))
Or, to be more terse:
100.times {|i| #stars_array << Star.new('unknown_star',i) }
A few things to fix to make it work. In your loop you're assigning a new value to the array rather than appending to it. Secondly, in your loop you're using a local variable stars_array instead of the instance variable #stars_array.
Your initialize method should look like this:
def initialize(part_of_solar_system)
#part_of_solar_system = part_of_solar_system
#stars_array = []
for i in 0..99
#stars_array << Star.new('unknown_star', i)
end
end
Also, you might want to revisit your Ruby idioms, like preferring snake_case to camelCase for variable names and avoiding for loops in favor of each, e.g.
def initialize(part_of_solar_system)
#part_of_solar_system = part_of_solar_system
#stars_array = []
(0..99).each { |i| #stars_array << Star.new('unknown_star', i) }
end

save multiple parameters in a loop, rails controller

I've created a form with about 40 fields available to edit, I'm trying to save them to a database using the controller. I currently have this code:
c = Form.find(params[:id])
if c
params.each do |k,v|
c.k = params[:v]
end
Which doesn't work, I get this error: undefined method 'k='
if I was going to write them all out manually it would look like this:
c = Form.find(params[:id])
if c
c.title = params[:title]
c.reference = params[:reference]
....
etc.
Assuming that you're trying to update the attributes on your Form record based on what gets passed into params, try this as a basic outline:
c = Form.find_by_id(params[:id])
if c
params.each do |k, v|
c[k] = v
end
c.save!
end
Your original code's use of params[:v] was probably not doing what you were intending, and you really meant for it to be params[:k] instead. However there's actually no need to look up the value for that key inside the loop like that because you already have the value at hand in v.
Here's a quick rundown on the ways of interacting with ActiveRecord attributes: http://www.davidverhasselt.com/2011/06/28/5-ways-to-set-attributes-in-activerecord/
i dont know what you are trying todo but your code seems to be very odd. Solution is as follow
c.send "#{k}=", params[:v]
What about
c = Form.find(params[:id])
c.update_attributes(params[:form])
Note that I guessed the [:form] part in the second line, it depends on your form. check your html source, and see if your fields are something like this:
<input name="form[field_name]" ...
As you see, name contains an "array like" form. Check your HTML source and adapt (so if its name="foo[field_name]", you need to use c.update_attributes(params[:foo]))

Doing something before saving a form used with HABTM

I currently have 3 tables.
snippet
tags
snippet_tags
I'm using HABTM.
So I did a form to save a snippet with tags. Keywords are in a text field, separated by commas.
What I need to do is to take the string from this text field, loop on the keywords, check if they exist, if not create them, and THEN save the snippet.
I tried with a before_save but it doesn't seem to go by that way..
So if you could help me, it'd great!
Thanks a lot!
I think JosephL's answer is pretty good. Although, I would do it all in the snippets_controller action:
def create
#snippet = Snippet.new(params[:snippet])
#snippet.tags = params[:tags].split(',').collect { |tag| Tag.find_or_create_by_name(tag) }
if #snippet.save
# do something when successful
else
# do something when saving failed
end
end
Sorry for that long, one-line statement. ;-)
I didn't test the code, but I hope it works.
Tag.find_or_create_by_name will do exactly that: when a tag with that name exists, it will return it, otherwise it will create the new tag on the fly and return that.
This way, the tags are already saved, before you call #snippet.save.
Please note, that I just assumed, how your variables and parameters are named.
Here is a version of your create method. The main change is not creating a Snippet_Tag. If your HABTM association is set up correctly then your snippet will have a tags collection which you can add your tags to. The collection will be persisted as Snippet_Tags by ActiveRecord. See the rails associations guide for more details on HABTM associations.
def create
# Creating the snippet
#snippet = Snippet.new
#snippet.title = params[:snippet][:title]
#snippet.content = params[:snippet][:content]
# loop through the tags
params[:snippet][:tags].split(',').collect do |tag_string|
tag_string.strip!
if tag_string.length > 0
# Find or create tag
tag = Tag.find_or_create_by_name(tag_string)
# Add tag to tags collection
#snippet.tags << tag
end
end
if #snippet.save
# do something when successful
else
# do something when saving failed
end
end
Use split to break your string into an array of the tags
Find each tag by name
If not found then create the tag
Add the tag to the snippet
Save the snippet (in your controller)
Example method to put in your snippet model
def add_tags(tag_list_string)
tag_array = tag_list_string.split ','
tag_array.each do |tag_name|
tag = (Tag.find_by_name(tag_name) || Tag.create(:name => tag_name))
self.tags << tag
end
end
Try before_update?

Resources