Writing out a rake task to import from csv, one would tend to write
Article.create(
category_id: row[0],
label: row[1],
name: row[2],
integer: row[3],
priority: row[4]
)
But these need to be refactored taking into account a meta-structure where
Class Import
has_many :importattributes
The following is syntactically incorrect
Article.create(
import.importattributes.each do |importattribute|
m_attr = importattribute.mapped_attribute
sequence = importattribute.position.to_i
m_attr: row[sequence]
end
)
on the line complaining about syntax error, unexpected ':', expecting 'end'
m_attr: row[sequence] If that line is commented out, other actions run as they do not parse this error beforehand.
I assume this is mistaken for the proper creation of array of attributes, because an update action, on a per attribute basis
import.importattributes.each do |importattribute|
m_attr = importattribute.mapped_attribute
sequence = importattribute.position.to_i
Article.update(m_attr: row[sequence])
end
does not raise the error.
This is not a valid syntax m_attr: row[sequence]. When used as an argument for update it is treated as a hash { m_attr: row[sequence] } and curly brackets can be omitted.
Article.create(
import.importattributes.each do |importattribute| # returns `import.importattributes`
# and used as argument for `create`
# nothing from `each` block is used
m_attr = importattribute.mapped_attribute # `m_attr` is unused
sequence = importattribute.position.to_i
m_attr: row[sequence] # invalid syntax
# at the very least it has to be wrapped in {} and used with hashrocket syntax
# to make use of m_attr variable.
# { m_attr => row[sequence] }
end
)
create accepts a hash of attributes to create a single record or an array of hashes to create multiple records. The return value of the import logic has to be one of these two options. For clarity, it can be done in three steps:
attributes = {}
import.importattributes.each do |importattribute|
attribute_name = importattribute.mapped_attribute
csv_column = importattribute.position.to_i
attributes[attribute_name] = row[csv_column] }
end
Article.create(attributes)
or in one step using inject
Article.create(
import.importattributes.inject({}) do |attributes, importattribute|
attributes[importattribute.mapped_attribute] = row[importattribute.position.to_i]
attributes
end
)
Related
I am trying to run a program from a book that I am learning ruby from, but I keep getting this error no matter how I try to fix it
ex41.rb:70:in `convert': wrong number of arguments (0 for 2) (ArgumentError)
from ex41.rb:117:in `block (2 levels) in <main>'
from ex41.rb:113:in `each'
from ex41.rb:113:in `block in <main>'
from ex41.rb:108:in `loop'
from ex41.rb:108:in `<main>'
I do understand that I have the wrong number of arguments, but the thing is I can't find where I'm getting them wrong, could someone please explain this to me? For convienience I put (LINE XXX(as a number)) by the lines that cause me trouble. Thank you.
# calls external file from web
require 'open-uri'
# assigns WORD_URL to website
WORD_URL = "http://learncodethehardway.org/words.txt"
# creates empty array WORDS
WORDS =[]
# creates hash PHRASES
PHRASES = {
"class ### < ### \nend" =>
"Make a class named ### that is-a ###.",
# my assumption is that ### ### and the like will sb in words, lets find
out
"class ###\n\tdef initialize(###)\n\tend\nend" =>
"class ### has-a initialize that takes ### parameters.",
"class ###\n\tdef ***(###)\n\tend\nend" =>
"class ### has-a function named *** that takes ### parameters.",
"*** = ###.new()" =>
"Set *** to an instance of class ###.",
"***.***(###)" =>
"From *** get the *** function, and call it with parameters ###",
"***.*** = '***'" =>
"From ***, get the *** attribute and set it equal to '***'."
}
# creates variable for first argument if argument is english
PHRASE_FIRST = ARGV[0] == "english"
# opens WORD_URL function and creates function f
open(WORD_URL) {|f|
# for every line in f chomps break character and pushes out word to f
f.each_line {|word| WORDS.push(word.chomp)}
}
# defines function craft_names with variables rand_words, snippet and
pattern, and assigns caps value to false
def craft_names(rand_words, snippet, pattern, caps=false)
# assigns vriable names to snippet.scan on pattern maps and does the
following
function
names = snippet.scan(pattern).map do
# assigns word to rand_words that have popped off the end
word = rand_words.pop()
# Guessing again, maybe capitalizes the first word
caps ? word.capitalize : word
# ends do
end
# returns names twice
return names * 2
# ends craft_names function
end
# defines function craft_params with variables rand_words snippet, and
pattern
def craft_params(rand_words, snippet, pattern)
# assigns names to action scan with variable pattern with action
# length on beginning part of array to snippet and runs the following
# function using map
names = (0...snippet.scan(pattern).length).map do
# assigns variable param_count to an action that takes a random 3 and adds one to it
param_count = rand(3) + 1
# assigns variable params to one long ass action that maps 0 to param_count
value and pops out the last word
params = (0...param_count).map { |x| rand_words.pop() }
# joins params list, (I'm sure its a list) with string ,
params.join(", ")
# ends names for loop
end
# returns names twice
return names * 2
# ends craft_params function
end
# defines convert function with variables snippet and phrase
(LINE 70) def convert(snippet, phrase)
# sords words randomly and assigns words to rand_words
rand_words = WORDS.sort_by {rand}
# assigns class names to function craft mnames on rand_words, snippet /###/, and caps=true
class_names = craft_names(rand_words, snippet, /###/, caps=true)
# assigns other_names to craft_names with variables rand_words, snippet, and /\*\*\*/
other_names = craft_names(rand_words, snippet, /\*\*\*/)
# assigns param_names to craft_params with rand_words, snippet, and ### as variables
param_names = craft_params(rand_words, snippet, /###/)
# assigns empty array results
results = []
# on all variables snippet and phrase matchups perform the function
sentence
[snippet, phrase].each do |sentence|
# fake class names, also copies sentence
result = sentence.gsub(/###/) {|x| param_names.pop}
# fake other names
result.gsub!(/\*\*\*/)
# fake parameter lists
result.gsub!(/###/)
# returns result
results.push(result)
# ends function
end
# returns results
return results
# ends convert function
end
# keep going until they hit ctrl-d
# continual loop
(LINE 108)loop do
# assigns variable snippets to Prases.keys and sorts randomly
snippets = PHRASES.keys().sort_by {rand}
# for snippet in snippets
(LINE113) snippets.each do |snippet|
# assigns PHRASES on snippet to phrase
phrase = PHRASES[snippet]
# asssigns question and answer to converted snippet and phrase
#respectively
(LINE117)question, answer = convert[snippet, phrase]
# if values in phrase firs are equal to answer, question
if PHRASE_FIRST
# question, answer = answer, question
question, answer = answer, question
# ends if
end
# prints question, two line breaks, and > without line break
print question, "\n\n> "
# closes program unless input
exit(0) unless $stdin.gets
# prints line break ANSWER: answer line break line break
puts "\nANSWER: %s\n\n"
# ends unless
end
# ends loop
end
TL;DR – Use convert(snippet, phrase) instead of convert[snippet, phrase]
When you write
convert[snippet, phrase]
... it is equivalent to:
convert()[snippet, phrase]
Adding a space:
convert [snippet, phrase]
... would call the method with an array:
convert([snippet, phrase])
To actually call the method with two arguments, use:
convert(snippet, phrase)
or
convert snippet, phrase
So what I am doing is iterating over various versions of snippet of code (for e.g. Associations.rb in Rails).
What I want to do is just extract one snippet of the code, for example the has_many method:
def has_many(name, scope = nil, options = {}, &extension)
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
Reflection.add_reflection self, name, reflection
end
At first I was thinking of just searching this entire file for the string def has_many and then saving everything between that string and end. The obvious issue with this, is that different versions of this file can have multiple end strings within the method.
For instance, whatever I come up with for the above snippet, should also work for this one too:
def has_many(association_id, options = {})
validate_options([ :foreign_key, :class_name, :exclusively_dependent, :dependent, :conditions, :order, :finder_sql ], options.keys)
association_name, association_class_name, association_class_primary_key_name =
associate_identification(association_id, options[:class_name], options[:foreign_key])
require_association_class(association_class_name)
if options[:dependent] and options[:exclusively_dependent]
raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.' # ' ruby-mode
elsif options[:dependent]
module_eval "before_destroy '#{association_name}.each { |o| o.destroy }'"
elsif options[:exclusively_dependent]
module_eval "before_destroy { |record| #{association_class_name}.delete_all(%(#{association_class_primary_key_name} = '\#{record.id}')) }"
end
define_method(association_name) do |*params|
force_reload = params.first unless params.empty?
association = instance_variable_get("##{association_name}")
if association.nil?
association = HasManyAssociation.new(self,
association_name, association_class_name,
association_class_primary_key_name, options)
instance_variable_set("##{association_name}", association)
end
association.reload if force_reload
association
end
# deprecated api
deprecated_collection_count_method(association_name)
deprecated_add_association_relation(association_name)
deprecated_remove_association_relation(association_name)
deprecated_has_collection_method(association_name)
deprecated_find_in_collection_method(association_name)
deprecated_find_all_in_collection_method(association_name)
deprecated_create_method(association_name)
deprecated_build_method(association_name)
end
Assuming that each value is stored as text in some column in my db.
How do I approach this, using Ruby's string methods or should I be approaching this another way?
Edit 1
Please note that this question relates specifically to string manipulation via using a Regex, without a parser.
As discussed, this should be done with a parser like Ripper.
However, to answer if it can be done with string methods, I will match the syntax with a regex, provided:
You can rely on indentation i.e. the string has the exact same characters before "def" and before "end".
There are no multiline strings in between that could simulate an "end" with the same indentation. That includes multine strings, HEREDOC, %{ }, etc.
Code
regex = /^
(\s*) # matches the indentation (we'll backreference later)
def\ +has_many\b # literal "def has_many" with a word boundary
(?:.*+\n)*? # match whole lines - as few as possible
\1 # matches the same indentation as the def line
end\b # literal "end"
/x
subject = %q|
def has_many(name, scope = nil, options = {}, &extension)
if association.nil?
instance_variable_set("##{association_name}", association)
end
end|
#Print matched text
puts subject.to_enum(:scan,regex).map {$&}
ideone demo
The regex relies on:
Capturing the whitespace (indentation) with the group (\s*),
followed by the literal def has_many.
It then consumes as few lines as it can with (?:.*+\n)*?.
Notice that .*+\n matches a whole line
and (?:..)*? repeats it 0 or more times. Also, the last ? makes the repetition lazy (as few as possible).
It will consume lines until it matches the following condition...
\1 is a backreference, storing the text matched in (1), i.e. the exact same indentation as the first line.
Followed by end obviously.
Test in Rubular
I am attempting to write my own solution to a Ruby exercise from Rubymonk where the purpose is to create three methods (add, subtract, and calculate) so when 'calculate' is called you can determine whether or not numbers are added or subtracted based on what is passed in. I am receiving the following error:
main:11: syntax error, unexpected '=', expecting ')' def calculate(*numbers, options={})
Can anyone tell me what the issue is with my code? Thanks for any and all help!
def add(*numbers)
numbers.inject(0) {|sum, number| sum + number}
end
def subtract(*numbers)
numbers.inject{|diff, number| diff - number}
end
def calculate(*numbers, options={})
result = add(numbers) if options.empty?
result = add(numbers) if options[:add]
result = subtract(numbers) if options[:subtract]
result
end
def calculate(*numbers, options={})
is not a valid method definition b/c *numbers takes the place a variable number of arguments. You have two options as I see it -
def calculate(options={}, *numbers)
or
def calculate(*args)
numbers, options = args[0..-2], args[-1] || {}
if you want to keep the same argument order
The splat argument *numbers needs to be the last argument. Otherwise, how would Ruby know when to treat the last argument as options or as the last number?
You can use (*numbers, options) (without a default value), but that would require that you always pass an options hash to the method (otherwise your last number will be set as the options variable instead).
Try this way:
def calculate(options={},*numbers)
Using optional arguments after the fully optional argument ( the * notation) do not work since it creates an ambiguity.
Read more at:
http://www.skorks.com/2009/08/method-arguments-in-ruby/
You can't use both a splat and a param with a default as last argument, this is too ambiguous for the parser (how to know that the last arg passed is meant to be the options?)
you can work around this in many ways ; one idiom from rails (active support) is :
def calculate(*args)
options = args.extract_options!
# ...
end
where extract_options! is a monkey-patch to Array from ActiveSupport defined as follow :
def extract_options!
last.is_a?(::Hash) ? pop : {}
end
as a side note :
an options hash is not really usefull here. you could pass in just a symbol as first argument, maybe.
if you use a hash, logic could be simpler :
def calculate(*args)
options = args.extract_options!
method = options.fetch(:method, :add)
send method, *args
end
on add, you don't need inject(0), injectuses the first element of your array as a first "memo" value if you don't provide one
you can pass a symbol to inject, which will be the method called on your "memo" value, with "next value" as argument :
(1..10).inject(:+)
# this is the same as
(1..10).inject{ |memo, next| memo + next }
# or, more exactly
(1..10).inject{ |memo, next| memo.send :+, next }
I am using Ruby on Rails 3 and I would like to know what means the presence of a * operator near a function argument and to understand its usages in others scenarios.
Example scenario (this method was from the Ruby on Rails 3 framework):
def find(*args)
return to_a.find { |*block_args| yield(*block_args) } if block_given?
options = args.extract_options!
if options.present?
apply_finder_options(options).find(*args)
else
case args.first
when :first, :last, :all
send(args.first)
else
find_with_ids(*args)
end
end
end
This is the splat operator, which comes from ruby (and is thus not rails specific). It can be applied in two ways depending on where it is used:
to "pack" a number of arguments into an array
to split up an array into an argument list
In your function, you see the splat operator used in the function definition. The result is that the function accepts any number of arguments. The complete argument list will be put into args as an array.
def foo(*args)
args.each_with_index{ |arg, i| puts "#{i+1}. #{arg}" }
end
foo("a", "b", "c")
# 1. a <== this is the output
# 2. b
# 3. c
The second variant would be when you consider the following method:
def bar(a, b, c)
a + b + c
end
It requires exactly three arguments. You can now call this method like follows
my_array = [1, 2, 3]
bar(*my_array)
# returns 6
The splat applied in this case to the array will split it and pass each element of the array as an individual parameter to the method. You could do the same even by calling foo:
foo(*my_array)
# 1. 1 <== this is the output
# 2. 2
# 3. 3
As you can see in your example method, these rules do apply to block parameters in the same way.
This is a splat argument, which basically means that any 'extra' arguments passed to the method will all be assigned to *args.
I want to write a function that allows users to match data based on a regexp, but I am concerned about sanitation of the user strings. I know with SQL queries you can use bind variables to avoid SQL injection attacks, but I am not sure if there's such a mechanism for regexps. I see that there's Regexp.escape, but I want to allow valid regexps.
Here is is the sample function:
def tagged?(text)
tags.each do |tag|
return true if text =~ /#{tag.name}/i
end
return false
end
Since I am just matching directly on tag.name is there a chance that someone could insert a Proc call or something to break out of the regexp and cause havoc?
Any advice on best practice would be appreciated.
Interpolated strings in a Regexp are not executed, but do generate annoying warnings:
/#{exit -3}/.match('test')
# => exits
foo = '#{exit -3}'
/#{foo}/.match('test')
# => warning: regexp has invalid interval
# => warning: regexp has `}' without escape
The two warnings seem to pertain to the opening #{ and the closing } respectively, and are independent.
As a strategy that's more efficient, you might want to sanitize the list of tags into a combined regexp you can run once. It is generally far less efficient to construct and test against N regular expressions than 1 with N parts.
Perhaps something along the lines of this:
class Taggable
def tags
#tags
end
def tags=(value)
#tags = value
#tag_regexp = Regexp.new(
[
'^(?:',
#tags.collect do |tag|
'(?:' + tag.sub(/\#\{/, '\\#\\{').sub(/([^\\])\}/, '\1\\}') + ')'
end.join('|'),
')$'
].to_s,
Regexp::IGNORECASE
)
end
def tagged?(text)
!!text.match(#tag_regexp)
end
end
This can be used like this:
e = Taggable.new
e.tags = %w[ #{exit-3} .*\.gif .*\.png .*\.jpe?g ]
puts e.tagged?('foo.gif').inspect
If the exit call was executed, the program would halt there, but it just interprets that as a literal string. To avoid warnings it is escaped with backslashes.
You should probably create an instance of the Regexp class instead.
def tagged?(text)
return tags.any? { |tag| text =~ Regexp.new(tag.name, Regexp::IGNORECASE) }
end