Ruby on Rails merge several objects into one - ruby-on-rails

I have following problem:
I have a Class named Foo and some instances of this Class like this:
#foo1
#foo2
#foo3
Each of these instances has an attribute called :description with a text in it like this:
#foo1.description = "Lorem"
#foo2.description = "ipsum"
#foo3.description = "dolore"
Now I would like to merge/combine the three objects above so that I afterward have only one instance like this:
#foo1.description = "Lorem ipsum dolore"
Has anyone an idea how i could do this merging?
Thanks.
EDIT MORE CONTEXT:
I have a class named Risk and a lot of instances.
The attributes of an instance of the Risk class are id, description, issue, references and target_ids as an instance of the Risk class can has_many targets.
In my Risk Index view where it displays all Risks I'd like to have an additional checkbox column where i can check all the risks I'd like to merge. Then there is a button called "Merge" and if the button is pressed all the Risks which are checked in the checkbox should be merged into the first one which is checked.

#foo1.description = "Lorem"
#foo2.description = "ipsum"
#foo3.description = "dolore"
#foos = #foo1 + #foo2 + #foo3
desc_array = []
#foos.each do |foo|
desc_array << foo.description
end
#foo1.description = desc_array.join
#foo1.description.save()

you could do following:
descriptions = Foo.where(:id => [#foo1, #foo2, #foo3]).map(&:description)
new_description = descriptions.join
#foo1.description = new_description
#foo1.save!
#foo2.destroy
#foo3.destroy

Related

Getting a model's children

If I have a model, ie. User, and each user has_many :animals, how do I get a retrieve the list of all of a particular set of User's animals?
I'd like to do something like this:
a = User.where(:last_name => 'Statham').animals
and have a be a list of the Animal objects. I assume I'm missing something trivial and simple.
Try this:
Animal.where(user_id: User.where(last_name: "Statham"))
Yep, simple is right. You're getting a lot of users there, so you need to iterate through them.
stathams = User.where(:last_name => 'Statham')
stathams.each { |statham| statham.animals }
And if you want to avoid duplicates:
animals = []
stathams = User.where(:last_name => 'Statham')
stathams.each { |statham| animals << statham.animals }
animals.uniq!

Creating a record from a third-party object with same field names?

I have a Charge model in my database with field names that match up to the fields returned from a third-party API.
charge = ThirdPartyChargeAPI.find(1)
Charge.create do |e|
e.object = charge.object
e.paid = charge.paid
e.amount = charge.amount
e.currency = charge.currency
e.refunded = charge.refunded
e.amount_refunded = charge.amount_refunded
e.failure_message = charge.failure_message
e.failure_code = charge.failure_code
e.description = charge.description
e.metadata = charge.metadata
e.captured = charge.captured
e.balance_transaction = charge.balance_transaction
e.customer = charge.customer
e.invoice = charge.invoice
e.created = charge.created
end
Seems painfully redundant, though. Is there some way to merge this without having to basically set every single field manually?
Assuming there's no way to get a direct hash from the API (I would imagine there would be, since it's probably coming in as XML or JSON), you could try a direct map of instance variables:
Charge.create do |c|
charge.instance_variables.each do |var|
value = charge.instance_variable_get(var)
c.instance_variable_set(var, value)
end
end
This is making some pretty bold assumptions about the structure of the charge you're getting back from the API though - any instance variable in it that you don't want will be included.

save information from another table

Hi everyone at this time i have two tables:
clientesultimasgestiones
clientesgestiones
And I want to put the whole information from clientesgestiones to clientesultimasgestiones but I want to save it field by field, at this momento I have this
cnx = ActiveRecord::Base.connection
cnx.execute("truncate table clientesultimasgestiones")
#informacion = Clientesgestion.all
#informacion.each do |f|
#clientesultimasgestion = Clientesultimasgestion.new
#clientesultimasgestion.save(f)
Here will be the code to save field by field from clientesgestiones table to the another one
end
Thanks for your help
EDIT: Finally i did it this way:
cnx.execute("truncate table clientesultimasgestiones")
#informacion = Clientesgestion.all
#informacion.each do |f|
l = Clientesultimasgestion.new
l.persona_id = f.persona_id
l.fecha_gestion = f.fecha_gestion
l.clientestipologia_id = f.clientestipologia_id
l.observacion = f.observacion
l.user_id = f.user_id
l.fecha_acuerdo = f.fecha_acuerdo
l.valor_apagar = f.valor_apagar
l.clientestipologiaanterior_id = f.clientestipologiaanterior_id
l.clientesobligacion_id = f.clientesobligacion_id
l.save
end
Thanks a lot :)
I would replace:
#clientesultimasgestion.save(f)
with:
#clientesultimasgestion.update_attibutes(f.attributes)
Also, seems what you want is to copy a table, see https://stackoverflow.com/a/13237661/1197775.
I think this question will help you to get lists of attributes and values.
After this, you need to set dynamically fields, for this purpose you can use method send. Something like this:
#clientesultimasgestion.send("#{field_name}=",field_value)

Using a method while looping through an array in ruby

I am using ruby-aaws to return Amazon Products and I want to enter them into my DB. I have created a model Amazonproduct and I have created a method get_amazon_data to return an array with all the product information. When i define the specific element in the array ( e.g. to_a[0] ) and then use ruby-aaws item_attributes method, it returns the name I am searching for and saves it to my DB. I am trying to iterate through the array and still have the item_attributes method work. When i don't define the element, i get this error: undefined method `item_attributes' for #Array:0x7f012cae2d68
Here is the code in my controller.
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each { |name|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = #arr.item_attributes.title.to_s
}
EDIT: Code in my model to see if that helps:
class Amazonproduct < ActiveRecord::Base
def self.get_amazon_data(r)
resp = Amazon::AWS.item_search('GourmetFood', { 'Keywords' => 'Coffee Maker' })
items = resp.item_search_response.items.item
end
end
Thanks for any help/advice.
I'm not familiar with the Amazon API, but I do observe that #arr is an array. Arrays do not usually have methods like item_attributes, so you probably lost track of which object was which somewhere in the coding process. It happens ;)
Try moving that .item_attributes call onto the object that supports that method. Maybe amazonproduct.get_amazon_data(:r), before its being turned into an array with to_a, has that method?
It's not quite clear to me what your classes are doing but to use #each, you can do something like
hash = {}
[['name', 'Macbook'], ['price', 1000]].each do |sub_array|
hash[sub_array[0]] = sub_array[1]
end
which gives you a hash like
{ 'name' => 'Macbook', 'price' => 1000 }
This hash may be easier to work with
#product = Product.new
#product.name = hash[:name]
....
EDIT
Try
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each do |aws_object|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = aws_object.item_attributes.title.to_s
end
end

Django Admin: Many-to-Many listbox doesn't show up with a through parameter

I have the following models:
class Message(models.Model):
date = models.DateTimeField()
user = models.ForeignKey(User)
thread = models.ForeignKey('self', blank=True, null=True)
...
class Forum(models.Model):
name = models.CharField(max_length=24)
messages = models.ManyToManyField(Message, through="Message_forum", blank=True, null=True)
...
class Message_forum(models.Model):
message = models.ForeignKey(Message)
forum = models.ForeignKey(Forum)
status = models.IntegerField()
position = models.IntegerField(blank=True, null=True)
tags = models.ManyToManyField(Tag, blank=True, null=True)
In the admin site, when I go to add/change a forum, I don't see the messages listbox as you'd expect. However, it shows up if I remove the 'through' parameter in the ManyToManyField declaration. What's up with that? I've registered all three models (plus Tag) to the admin site in admin.py.
TIA
Documentation says:
When you specify an intermediary model using the through argument to a
ManyToManyField, the admin will not display a widget by default.
But it's probably possible to display M2M fields in the admin change view even if the through attribute is defined.
class ForumAdminForm(forms.ModelForm):
mm = forms.ModelMultipleChoiceField(
queryset=models.Message.objects.all(),
widget=FilteredSelectMultiple(_('ss'), False, attrs={'rows':'10'}))
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
initial = kwargs.setdefault('initial', {})
initial['mm'] = [t.service.pk for t in kwargs['instance'].message_forum_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self, commit)
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
messages = [s for s in self.cleaned_data['ss']]
for mf in instance.message_forum_set.all():
if mf.service not in messages:
mf.delete()
else:
messages.remove(mf.service)
for message in messages:
Message_forum.objects.create(message=message, forum=instance)
self.save_m2m = save_m2m
return instance
class Meta:
model = models.Forum
class ForumAdmin(admin.ModelAdmin):
form = ForumAdminForm
Take a look at the official documentation:
I learned a lot from #Fedor's answer, but some comments and cleanup may be still beneficial.
class ForumAdminForm(forms.ModelForm):
messages = forms.ModelMultipleChoiceField(
queryset=Message.objects.all(),
widget=FilteredSelectMultiple('Message', False))
# Technically, you don't need to manually set initial here for ForumAdminForm
# However, you NEED to do the following for MessageAdminForm
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
# a record is being changed. building initial
initial = kwargs.setdefault('initial', {})
initial['messages'] = [t.message.pk for t in kwargs['instance'].message_forum_set.all()]
super(ForumAdminForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
if not self.is_valid():
raise HttpResponseForbidden
instance = super(ForumAdminForm, self).save(self, commit)
def save_m2m_with_through():
messages = [t for t in self.cleaned_data['messages']
old_memberships = instance.message_forum_set.all()
for old in old_memberships:
if old.message not in messages:
# and old membership is cleaned by the user
old.delete()
for message in [x for x in messages not in map(lambda x: x.message, old_memberships)]:
membership = Member_forum(message=messsage, forum=instance)
# You may have to initialize status, position and tag for your need
membership.save()
if commit:
save_m2m_with_through()
else:
self.save_m2m = save_m2m_with_through
return instance
class Meta:
model = Forum
fields = {'name', 'messages')
There's one caveat: if you have another many-to-many relationship in the models (that is without through), super(ForumAdminForm, self).save(self, commit) will set self.save_m2m in case commit is False. However, calling this would cause an error, because this function also tries to save the many-to-many with through as well. You may need to save all other many-to-many relationship manually, or catch the exception, or else.
Django admin nicely support many-to-many intermediary models that using the through argument .
For example you have these Person and Group models with intermediate Membership model:
models.py
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Now in admin.py file ,
Define an inline class for the intermediate Membership model:
#admin.register(Membership)
class MembershipInline(admin.TabularInline):
model = Membership
extra = 1
And use them in admin views of models:
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
inlines = (MembershipInline,)
#admin.register(Group)
class GroupAdmin(admin.ModelAdmin):
inlines = (MembershipInline,)
More info in official docs:
Models,
Admin

Resources