Rails set has_many value dynamically - ruby-on-rails

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

Related

Rails use attributes as string in method

I am using the gem Alchemist to make unit conversions.
Given this working in my model:
class Item < ApplicationRecord
def converted
quantity = 1
quantity.kg.to.g
end
end
How do I make kg and g dynamic? Like:
quantity.unit_purchase.to.unit_inventory
unit_purchase and unit_inventory are attributes (strings) of the class, corresponding to values such as kg, g and so on.
So perhaps something like:
x = self.unit_purchase
y = self.unit_inventory
quantity.x.to.y
But I'm having hard time to find the syntax.
If you really want to do this the hard way:
unit_purchase = :kg
unit_inventory = :g
quantity.send(unit_purchase).to.send(unit_inventory)
That depends on knowing with absolute certainty that the two arguments are valid and aren't something hostile supplied by the user.
A safer way is to define a more arbitrary conversion method like:
quantity.convert(from_unit: unit_purchase, to_unit: unit_inventory)
Where that can check the arguments accordingly and raise on unexpected values.

.where.not with empty array

I have a problem trying to work with a NOT IN query (using Rails 4/Postgres, for reference) in an elegant way. I'm trying to get a list of all objects of a certain model that don't show up in a join table for a certain instance. It works , when you try a NOT IN query with an empty array, it throws an error because you can't look for NOT IN NULL.
The below code now works, but is there a better way than to use an unintuitive conditional to make a pseudo-null object?
def characters_selected
self.characters_tagged.pluck(:name)
end
def remaining_characters
characters = self.characters_selected
characters = ["SQL breaks if this is null"] if characters.empty?
# this query breaks on characters_selected == [] without the above line
Character.where("name NOT IN (?)", characters )
end
This is the ActiveRecord way:
def remaining_characters
characters = self.characters_selected
Character.where.not(:name => characters)
end
When characters.empty? the where clause becomes "WHERE (1=1)".

Unexpected syntax error in block

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?

how to delete specific characters in ruby?

There is already created record, like
Company "Life"
How to make this record to the species
сompany-life
I used parameterize, but it turns:
company-quot-life-quot
As I understand, .gsub(""", "") is not suitable for implementation, since to create too large list of exceptions
Is there may be a way to make record in raw format? (to parameterize later)
thanks in advance!
Here is a non-Rails approach:
require 'cgi'
str = 'Company "Life"'
puts CGI.unescape_html(str).gsub(/"/, '').gsub(/\s+/, '-').downcase
# => company-life
And a pure regex solution:
puts str.gsub(/&\w+;/, '').gsub(/\s+/, '-').downcase
# => company-life
And if you are inside Rails(thanks to #nzifnab):
str.gsub(/&\w+;/, '').parameterize
As #meager said, you shouldn't be storing the html-encoded entities in the database to begin with, how did it get in there with "? Theoretically this would work:
class Page < ActiveRecord::Base
before_validation :unescape_entities
private
def unescape_entities
self.name = CGI.unescape_html(name)
end
end
But I'm still curious how name would be getting there in the first place with html entities in it. What's your action/form look like?
"Company "Life"".html_safe.parameterize
"Company "Life"".gsub(/&[^;]+;/, "-").parameterize.downcase
# => "company-life"
Firstly, gsub gets rid of html entities, then parameterize gets rid from all but Ascii alphanumeric (and replaces them with dash), then downcase. Note that "_" will be preserved too, if you don't like them, another gsub('_', '-') is needed.

Ruby on Rails: Using shovel operator to update a string attribute on a model does not make the model dirty

We ran into an interesting problem today. It seems that if you use the shovel operator to concatenate a string attribute on an ActiveRecord model, it doesn't make the model dirty. For example:
e = Employee.first
e.name << "asdf"
e.name_changed? # returns false
e.changed? # returns false
This makes sense since the shovel operator updates a string without making a copy of it, where the += operator will make a copy of the string. I don't see how ActiveRecord could possibly know that something changed if you use the shovel operator.
Has anyone else seen this? Is the solution to just use += instead of << when concatenating strings?
The solution is was you write.
Or you can mark before that your attibute will_change
e = Employee.first
e.name_will_change!
e.name << "asdf"
e.name_changed? # => true
It's mark on the API documentation. ActiveModel::Dirty

Resources