Unexpected syntax error in block - ruby-on-rails

I am basically copying one objects information to another. The code or approach in general might not be the most well-thought out, but that's not my problem right now.
This is the error:
syntax error, unexpected '=', expecting keyword_end
original.send("#{attribute}") = edited.send("#{attribute}")
^
What I'm doing is looping through all the attributes of object2 and then "copying" each one to object 1. I could make this specific for each model, but I wanted to have one single implement_changes method, that would work for each class basically. The copy model belongs_to :edited and :original through polymorphic associations.
class Copy < ActiveRecord::Base
def implement_changes
original = self.original_type.constantize.find(original_id)
edited = self.edited_type.constantize.find(edited_id)
accessible_attributes = original_type.constantize.accessible_attributes.to_a.select{|a| a != "slug"}
accessible_attributes.shift
accessible_attributes.each do |attribute|
original.send("#{attribute}") = edited.send("#{attribute}")
end
original.save!
end
Why doesn't that block work?? I don't get it. Is the usage of send correct here? It wouldn't let me do original.attribute.
Any help appreciated! :)

The method name for a setter includes the equals sign, and takes the new value as an argument. You might try:
original.send("#{attribute}=", edited.send(attribute))

Is there a reason you're not using ActiveResource::Base#dup?

Related

Rails set has_many value dynamically

I have an entity: book of class Book.
The entity class has has_many relation with other tables, pages for example.
Let's say that page_1 and page_2 are valid values that I'de like to save. The non-dynamic version would be something like:
entity.pages = [page_1, page_2]
How can I set this dynamically?
I tried using send (which works fine for has_one) with no luck:
attr = :pages # my dynamic attribute
book.send(attr) = [page_1, page_2]
# SyntaxError: unexpected '=', expecting end-of-input
# mc.send(:diagnoses, '=') = [s]
# ^
When I use << it seems to work:
book.send(attr) << page_1
but the issue is that I need to support deletion, e.g. if the book had page3, and now it has page1 and page2.
I don't want to use eval, both due to performance and security. Not sure it's related, but these dynamic attributes all have the same class - has__many with a dynamic condition.
The correct format is to call the setter (assignment) method. Which is usually the attribute followed by an equal sign. In your case, you want pages=
book.send(attr.to_s + '=', [page_1, page_2] )
Equivalent to
book.send('pages=', [page_1, page_2])
which is...
book.pages=([page_1, page_2])
or more conventionally written
book.pages = [page_1, page_2]
Try book.association(:pages).target

`initialize': wrong number of arguments (1 for 0)

I'm getting an error with the following example when running the program.
The error reads as such:
burger.rb:8:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
from burger.rb:19:in `new'
from burger.rb:19:in `<main>'
Any help is appreciated for this noob, the code is below:
$toppings = false
class Burger
AVAILABLE_TOPPINGS = ["lettuce", "tomato", "onion", "cheese"]
attr_reader :options
def initialize
#toppings = []
end
def order
print "how many burgers would you like? "
number = gets.chomp
puts "#{number} burgers coming right up boss!"
end
end
burger = Burger.new("lettuce")
burger.order
The error is telling you that the method initialize expects 0 argument, while you give it 1 ("lettuce" in Burger.new("lettuce")).
You need to make initialize expecting one argument:
def initialize(options)
#toppings = []
#options = options
end
As others have said your initializer is expecting no arguments but you're giving it lettuce. If you're using ruby 2.1 or later I would suggest using keyword arguments:
class Burger
TOPPINGS = %i[lettuce tomato onion cheese]
attr_reader :toppings
def initialize(toppings: [])
#toppings = TOPPINGS & toppings
end
end
This allows you do to Burger.new(toppings: [:lettuce]) which I feel is a lot more readable.
$toppings = false
is code smell. Globals are generally not necessary, and should only be used when you're absolutely sure they're needed. When you're first learning an OO language I think it's better to avoid them and learn about variable scoping.
In this case, you don't use it in your sample code, but you do use:
#toppings = []
(which is again not used elsewhere). It isn't a good idea to name a global variable the same as an instance variable because it's too easy to use one when you mean the other, and introduce a bug.

Using a Variable Name as Attribute in Rails

I want to DRY up my Rails code by making a common method that will be reused. In order to do so, I have to make some field/attributes and the class name that is used in the code variables, so it can work for the three models (and their fields) with the same code. I tried to learn from this question and this one, but I haven't been able to get it to work.
In my model, I have this:
def self.update_percentages
update_percentages_2(User, "rank", "top_percent")
end
def self.update_percentages_2(klass, rank_field, percent_field)
rank_class = (klass.name).constantize
total_ranks = rank_class.maximum(rank_field)
top_5 = (total_ranks * 0.05).ceil
rank_class.find_each do |f|
if f.send("#{rank_field}") <= top_5
f.send("#{percent_field}", 5)
f.save
end
end
end
With this code, I get ArgumentError: wrong number of arguments (1 for 0). When I start commenting lines out to narrow down the problem, it seems that the f.send("#{percent_field}", 5) causes the error.
And if I add:
percent_field = (percent_field).constantize
I get: Name Error: wrong constant name top_percent.
Can someone please help me determine what I'm doing wrong?
If you want to assign to an attribute, you need the method name with the equal sign:
f.send("#{percent_field}=", 5)
Also, this:
rank_class = (klass.name).constantize
is equivalent to this:
rank_class = klass
I would rewrite your method to update all qualifying records in on transaction.
def self.update_percentages_2(klass, rank_field, percent_field)
top_5 = ( klass.maximum(rank_field) * 0.05).ceil
klass.where("#{rank_field} <= ?", top_5).update_all(percent_field => 5)
end
BTW
Here is an answer to your original question.

Why doesn't this work? (More so Ruby than Rails)

def refresh_menu
#menu_pages = []
$menu_items.each do |id|
#menu_pages[id - 1] = Page.find(id)
end
end
$menu_items is just an array [1,2]. Obviously what I want to do is populate #menu_pages with all the pages found as per $menu_items.
Mind you,
#menu_pages = Page.all
works just fine. So how come I can't add them one-by-one with Page.find(id)?
The error returned:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.each
Why not just do?...
def refresh_menu
#menu_pages = Page.where(:id => $menu_items)
end
And in relation to the error, where is $menu_items defined?
nowk, thanks for the neat trick.
To those who might find this through search: don't forget to restart the server when you make changes to initializers.
Where are you defining $menu_items? From your error message, it looks like the refresh_menu method can't see it - and thus thinks it's nil.

Rails 3 Error Checking for Existence of Association

I has a model "modela" that has a has_many_through relationship with model "submodelb". In a controller I want to check if modela has any submodelb associated with it. I have tried the two code examples below; however, the both throw the error "undefined method `submodelbs'" if modela does not have any submodelbs. Please help me see what I am doing wrong.
Sample 1:
if !#modela.submodelbs.nil?
#submodelbs = #modela.submodelbs
else
#submodelbs = []
end
Sample 2:
if !#modela.submodelbs.empty?
#submodelbs = #modela.submodelbs
else
#submodelbs = []
end
You can use .present? which is the opposite of blank?
#submodelbs = #modela.submodelbs.present? ? #modela.submodelbs : []
But I think your problem is that #modela may be nil or you may have not defined associations correctly in the model.
The reader method produced by has_many_through always returns something that looks like an Array, so it should never return nil. So, can't you just return #modela.submodelbs always?
I use blank?
unless #modela.submodelbs.blank?
#modela has submodelbs
end
but error messege suggests that you may have something wrong with association definition...
Also what you are trying to achieve can be done with one-liner
#modela.submodelbs ||= []
if submodelbs are nil empty array will be assigned.
Why not just put your checks in a begin...rescue...end block?

Resources