Ruby, merge two different objects - ruby-on-rails

I need to merge to different object in my Ruby on Rails application. I have my Invoice object:
class Invoice < ActiveRecord::Base {
:id => :integer,
:user_id => :integer,
:description => :text,
......
:status => :string,
:price => :float
}
And my Payment object:
class Payment < ActiveRecord::Base {
:id => :integer,
:token => :string,
:invoice_id => :integer,
:created_at => :datetime,
:updated_at => :datetime,
:email => :string
}
With 1 to many relationship between them:
class Invoice < ActiveRecord::Base
has_many :payments
class Payment < ActiveRecord::Base
belongs_to :invoice
Now what I would like to do is to return the Invoice object and the :email and :created_at field of the payment object associated. Right now I return the two object with the zip function:
:invoices => (user.invoices.where(:hide => false).zip user.invoices.map{|x| x.payments.last}),
but that return an array of array:
[
[{invoice},{payment}],
[{invoice},{payment}],
...
]
What I want to return is something like:
[
{invoice, :email_payment, :created_at_payment},
{invoice_1, :email_payment, :created_at_payment},
...
]
How can I do that?

I would add email_payment and created_at_payment as methods to the invoice model, but you can achieve it with the following:
user.invoices.where(hide: false).map do |invoice|
invoice.attributes.merge({
email_payment: invoice.payments.last.email,
created_at_payment: invoice.payments.last.created_at
})
end

Related

Ruby - Query another table in a model definition

I want to query another table in a model definition. For instance, I have a table called miq_user_roles and I want to query and retrievesettings column value.
I tried adding the following
has_many :miq_user_roles
but when I try the where condition where(:settings => nil)
I get the error service_template doesn't have settings column. How can I query miq_user_roles for settings instead of service_template
service_template has a column called miq_group_id and its the id of miq_user_rolestable.
Following is the actual model definition where I want to include miq_user_roles table.
class ServiceTemplate < ApplicationRecord
include SupportsFeatureMixin
DEFAULT_PROCESS_DELAY_BETWEEN_GROUPS = 120
GENERIC_ITEM_SUBTYPES = {
"custom" => N_("Custom"),
"vm" => N_("Virtual Machine"),
"playbook" => N_("Playbook"),
"hosted_database" => N_("Hosted Database"),
"load_balancer" => N_("Load Balancer"),
"storage" => N_("Storage")
}.freeze
SERVICE_TYPE_ATOMIC = 'atomic'.freeze
SERVICE_TYPE_COMPOSITE = 'composite'.freeze
RESOURCE_ACTION_UPDATE_ATTRS = [:dialog,
:dialog_id,
:fqname,
:configuration_template,
:configuration_template_id,
:configuration_template_type].freeze
include CustomActionsMixin
include ServiceMixin
include OwnershipMixin
include NewWithTypeStiMixin
include TenancyMixin
include ArchivedMixin
include CiFeatureMixin
include_concern 'Filter'
include_concern 'Copy'
validates :name, :presence => true
belongs_to :tenant
has_many :service_templates, :through => :service_resources, :source => :resource, :source_type => 'ServiceTemplate'
has_many :services
has_many :service_template_tenants, :dependent => :destroy
has_many :additional_tenants, :through => :service_template_tenants, :source => :tenant, :dependent => :destroy
has_one :picture, :dependent => :destroy, :as => :resource, :autosave => true
belongs_to :service_template_catalog
belongs_to :zone
belongs_to :currency, :inverse_of => false
has_many :dialogs, -> { distinct }, :through => :resource_actions
has_many :miq_schedules, :as => :resource, :dependent => :destroy
has_many :miq_requests, :as => :source, :dependent => :nullify
has_many :active_requests, -> { where(:request_state => MiqRequest::ACTIVE_STATES) }, :as => :source, :class_name => "MiqRequest"
virtual_column :type_display, :type => :string
virtual_column :template_valid, :type => :boolean
virtual_column :template_valid_error_message, :type => :string
virtual_column :archived, :type => :boolean
virtual_column :active, :type => :boolean
default_value_for :internal, false
default_value_for :service_type, SERVICE_TYPE_ATOMIC
default_value_for(:generic_subtype) { |st| 'custom' if st.prov_type == 'generic' }
virtual_has_one :config_info, :class_name => "Hash"
scope :with_service_template_catalog_id, ->(cat_id) { where(:service_template_catalog_id => cat_id) }
scope :without_service_template_catalog_id, -> { where(:service_template_catalog_id => nil) }
scope :with_existent_service_template_catalog_id, -> { where.not(:service_template_catalog_id => nil) }
scope :displayed, -> { where(:display => true) }
scope :public_service_templates, -> { where(:display => true) }
supports :order do
unsupported_reason_add(:order, 'Service template does not belong to a service catalog') unless service_template_catalog
unsupported_reason_add(:order, 'Service template is not configured to be displayed') unless display
end
alias orderable? supports_order?
alias validate_order supports_order?
def self.with_tenant(tenant_id)
tenant = Tenant.find(tenant_id)
where(:tenant_id => tenant.ancestor_ids + [tenant_id])\
end
Add the table name:
where("miq_user_roles.settings" => nil)
Arel is nice, but you still have to use bits of SQL to get over issues like this.
You will have to use joins, to query on the associated table.
ServiceTemplate.joins(:miq_user_roles).where(miq_user_roles: { settings: nil })

API: Update model through an other related model

I'm trying make it possible to update a a LineItem trough a CreditNote. It's for an API, so I'm trying to update that trough a JSON.
My relational model is:
class TestCreditNote < ActiveRecord::Base
self.table_name = :credit_notes
has_many :line_items, :class_name => TestLineItem, :foreign_key => :artef_id
accepts_nested_attributes_for :line_items
end
class TestLineItem < ActiveRecord::Base
self.table_name = :line_items
attr_accessible :description
belongs_to :credit_note, :class_name => TestCreditNote, :foreign_key => :artef_id
end
When executing this test:
it "should update the sales line item record" do
put "api/v1/credit_notes/#{#credit_note.id}", { :test_credit_note => { :line_items => [{ :description => 'PEPITO'}] }}, http_headers
data = JSON.parse(response.body, :symbolize_names => true)
TestCreditNote.find(#sales_credit_note.id).line_item.description.should == 'PEPITO'
end
It fails because of:
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: line_items
I've add the attr_accesible :line_items_attributes
class TestCreditNote < ActiveRecord::Base
self.table_name = :credit_notes
has_many :line_items, :class_name => TestLineItem, :foreign_key => :artef_id
accepts_nested_attributes_for :line_items
attr_accessible :line_items_attributes
end
And the same in the test
it "should update the sales line item record" do
put "api/v1/credit_notes/#{#credit_note.id}", { :test_credit_note => { :line_items_attributes => [{:id => 1, :description => 'PEPITO'}] }}, http_headers
data = JSON.parse(response.body, :symbolize_names => true)
TestCreditNote.find(#sales_credit_note.id).line_item.description.should == 'PEPITO'
end

Rails rabl condition for class variable in view

In controller i have such code:
#bank_exchangers = ExchangerList.find(:all, :conditions => {:bank_id => params[:id]})
#currency_list = CurrencyList.all
#currencies = []
#currency_list.each do |c|
#currencies << CurrencyValue.find(:all, :conditions => {:currency_list_id => c.id}, :order => :updated_at)
end
#currencies.flatten!
and i have such models:
class CurrencyList < ActiveRecord::Base
attr_accessible :code, :country, :name
has_many :currency_values
end
class CurrencyValue < ActiveRecord::Base
attr_accessible :currency_list_id, :direction_of_exchange_id, :value
belongs_to :currency_list
belongs_to :exchanger_list
end
class ExchangerList < ActiveRecord::Base
attr_accessible :address, :bank_id, :exchanger_type_id, :latitude, :location_id, :longitude, :name
has_many :currency_values
end
i need to display for each ExchangerList it's CurrencyValue with some conditions, as i provided below... But main trouble is with rabl output:
i have such code:
collection #bank_exchangers, :root => "bank_exchangers", :object_root => false
attributes :id, :name, :address, :location_id, :latitude, :longitude, :exchanger_type_id
child #currencies do
attribute :value
end
as you can see, here for each #bank_exchangers i create node with it's #currencies... But i need to display node only for this #bank_exchangers iterator, if i would write in controller i would write something like:
#currencies << CurrencyValue.find(:all, :conditions => {:currency_list_id => c.id, :exchanger_list_id => param}, :order => :updated_at)
How to set something like this in view?
Becouse now my output is like:
{"bank_exchangers":[{"id":3,"name":"Банк *** ЦБУ №1","address":"г. Минск, ул. Московская, 13","location_id":8,"latitude":null,"longitude":null,"exchanger_type_id":1,"location_name":"Минск","exchanger_type_name":"normal","currency_values":[{"currency_value":{"value":8620.0}},{"currency_value":{"value":8620.0}},{"currency_value":{"value":8700.0}},{"currency_value":{"value":8700.0}},{"currency_value":{"value":8620.0}},{"currency_value":{"value":8700.0}},{"currency_value":{"value":11500.0}},{"currency_value":{"value":11100.0}}]},{"id":4,"name":"Банк ***","address":"г. Минск, Мясникова, 32","location_id":8,"latitude":null,"longitude":null,"exchanger_type_id":1,"location_name":"Минск","exchanger_type_name":"normal","currency_values":[{"currency_value":{"value":8620.0}},{"currency_value":{"value":8620.0}},{"currency_value":{"value":8700.0}},{"currency_value":{"value":8700.0}},{"currency_value":{"value":8620.0}},{"currency_value":{"value":8700.0}},{"currency_value":{"value":11500.0}},{"currency_value":{"value":11100.0}}]}]}
as you can see, for each bank_exchangers i create node with all currency_values data, but i need to put for each bank_exchangers in node only currency_values for this bank_exchangers parent....
How could i do this?
Sorry if something is not clear... i'm new...
Just how to set for my child #currencies in view some condition?
You can take advantage of the relation between ExchangerList and CurrencyValues:
child :currency_values do
attribute :value
end
If you have conditions, you can include these using a lambda:
if => lambda { |child| child.something? })
child(:currency_values, if => lambda { |currency_value| currency_value.something? }) do
attribute :value
end
You may also want to define a view for just the one ExchangerList object (i.e. show):
object #bank_exchanger, :object_root => false
attributes :id, :name, :address, :location_id, :latitude, :longitude, :exchanger_type_id
child(:currency_values, if => lambda { |currency_value| currency_value.something? }) do
attribute :value
end
Then simply have the collection extend this:
collection #bank_exchangers, :root => "bank_exchangers", :object_root => false
extends 'bank_exchangers/show'
Alternatively, if you add a 'currencies' method to to the ExchangerList model, you can call the method directly as an attribute via RABL. Some test code I wrote:
def test
test_array = []
1.upto(3) do |i|
test_array << {qty: i, px: 2*i}
end
return test_array
end
You can then simply call this as an attribute:
object #object
attribute :test
This results in the following JSON, which I believe is similar to the format you are trying to achieve:
test: [
{
qty: 1,
px: 2
},
{
qty: 2,
px: 4
},
{
qty: 3,
px: 6
}
],

rails 3 custom validation error messages in a join table, how?

How can I return errors messages from a cross reference table with multiple records when I trying to create those? I'm trying this:
## activity_set.rb
class ActivitySet < ActiveRecord::Base
has_many :activity_set_lessons
has_many :lessons, :through => :activity_set_lessons
validates :name, :presence => true
def activity_set_lessons=(data)
data.each_with_index do |v, i|
activity_set_lessons.build(
:lesson_id => v[:lesson_id],
:sort_order => i,
:weight_percentage => v[:weight_percentage]
)
end
end
end
## activity_set_lesson.rb
class ActivitySetLesson < ActiveRecord::Base
belongs_to :activity_set
belongs_to :lesson
validates :lesson_id, :presence => true
validates_each :weight_percentage do |record, attr, value|
record.errors.add :base, "woot" if value.blank?
end
end
This is the request data:
## params[:activity_set]
"activity_set" => {
"name" => "hshshshs",
"keywords" => "",
"activity_set_lessons" => [
{"weight_percentage" => "", "lesson_id"=>"4"},
{"weight_percentage" => "", "lesson_id"=>"5"}
]
}
Error messages from #activity_set when I do #save:
{
"errors":{
"activity_set_lessons":["is invalid","is invalid"]
},
"full_messages":[
"Activity set lessons is invalid","Activity set lessons is invalid"
]
}
I always got the same error message even if I'm adding a custom one in the join table. How can I return a message like: "woot 1 is wrong" or something like that, per validation?.
Thanks.
make use of accepts_nested_attributes_for
## activity_set.rb
class ActivitySet < ActiveRecord::Base
has_many :activity_set_lessons
has_many :lessons, :through => :activity_set_lessons
validates :name, :presence => true
accepts_nested_attributes_for :activity_set_lessons
end
view will look like
= form_for #activity_set do |f|
[activity_set form fields ]
= f.fields_for :activity_set_lessons do |p|
= p.select :lession_id
= p.select :weight_percentage

Rails 3 error: Object doesn't support #inspect on includes (left outer join)

I'm using Rails 3.
I have 3 models:
class Deal < ActiveRecord::Base
has_many :wishes, :class_name => "Wish"
has_many :wishers, :through => :wishes, :source => :user
end
class User < ActiveRecord::Base
has_many :wishes, :class_name => "Wish", :conditions => { 'wishes.wished' => true }
has_many :wished_deals, :through => :wishes, :source => :deal
end
class Wish < ActiveRecord::Base
belongs_to :user
belongs_to :deal
end
And i'm trying to create the following scope in the Deal model:
scope :not_wished_by_user, lambda { |user| includes(:wishes).where('wishes.wished' != true, 'wishes.user_id' => user) }
What i want is all the Deals, except those that are marked as 'wished' by the given user in the block. But whenever i do that includes, i get the following error:
ruby-1.9.2-head > u = User.all.first
ruby-1.9.2-head > Deal.not_wished_by_user(u)
(Object doesn't support #inspect)
=>
Also, placing it in a function doesn't work. Any idea what this could be?
Thanks!
EDIT: These are Wishes table migration
class CreateWish < ActiveRecord::Migration
def self.up
create_table :wishes do |t|
t.integer :deal_id
t.integer :user_id
t.boolean :wished, :default => true
t.boolean :collected, :default => false
t.datetime :collected_date
t.timestamps
end
add_index :wishes, [:deal_id, :user_id], :uniq => true
end
end
See Update below vv
Old answer
You are not using any Deal attributes for selects so try to move code into Wish class:
class Wish < ActiveRecord::Base
belongs_to :user
belongs_to :deal
scope :'wished?', lambda{ |f| where('wished = ?', f) }
scope :not_wished_by_user, lambda{|user| wished?(false).where('user_id = ?', user)}
end
Usage exmple and output:
ruby-1.9.2-p180 :023 > Wish.not_wished_by_user(User.first).to_sql
=> "SELECT \"wishes\".* FROM \"wishes\" WHERE (wished = 't') AND (user_id = 1)"
Is this correct result for you?
PS:
In the Deal you can leave proxy-method like:
class Deal < ActiveRecord::Base
has_many :wishes, :class_name => "Wish"
has_many :wishers, :through => :wishes, :source => :user
def self.not_wished_by_user(user)
Wish.not_wished_by_user(user)
end
end
Update1 (subquery)
class Deal < ActiveRecord::Base
has_many :wishes, :class_name => "Wish"
has_many :wishers, :through => :wishes, :source => :user
scope :deal_ids_not_wished_by_user, lambda { |user|
joins(:wishes).where('wishes.user_id = ?', user).where('wishes.wished = ?', false).select('deals.id')
}
scope :wished_by_user, lambda { |user|
where("id not in (#{Deal.deal_ids_not_wished_by_user(user).to_sql})")
}
end
Usage example and output:
ruby-1.9.2-p180 :023 > Deal.wished_by_user(User.first).to_sql
=> "SELECT \"deals\".* FROM \"deals\" WHERE (id not in (SELECT deals.id FROM \"deals\" INNER JOIN \"wishes\" ON \"wishes\".\"deal_id\" = \"deals\".\"id\" WHERE (wishes.user_id = 1) AND (wishes.wished = 'f')))"
UPD2 (railish outer join)
Deal class:
class Deal < ActiveRecord::Base
has_many :wishes, :class_name => "Wish"
has_many :wishers, :through => :wishes, :source => :user
scope :not_wished_excluded, lambda { |user|
joins('LEFT OUTER JOIN wishes on wishes.deal_id = deals.id').
where('wishes.user_id = ? OR wishes.user_id is null', user).
where('wishes.wished = ? OR wishes.wished is null', true)
}
end
Usage:
ruby-1.9.2-p180 :096 > Deal.not_wished_excluded(User.first).to_sql
=> "SELECT \"deals\".* FROM \"deals\" LEFT OUTER JOIN wishes on wishes.deal_id = deals.id WHERE (wishes.user_id = 1 OR wishes.user_id is null) AND (wishes.wished = 't' OR wishes.wished is null)"

Resources