How to get attribute of object in Model - Ruby on Rails - ruby-on-rails

How can I get the attribute first_name of an entity called Applicant when I'm in a different model called Billing. So far I am able to make it work however, what is returned is the object and not only the attribute.
The following is my code:
class Billing < ActiveRecord::Base
def self.to_csv
attributes=%w{tenant bill_type_dec total_amount created_at datetime_paid paid }
CSV.generate(headers:true) do |csv|
csv<<attributes
all.each do |bill|
csv <<attributes.map{|attr| bill.send(attr)}
end
end
end
def bill_type_dec
if bill_type!=nil
if bill_type==1
"Water"
else
"Electricity"
end
else
"#{description}"
end
end
def tenant
#applicants=Applicant.where(id: tenant_id)
#applicants.each do |appli|
"#{appli.first_name}"
end
end
end

You probably want to use .map instead of .each.
You can get all the names of the applicants in an array by doing this:
#applicants.map { |appli| appli.first_name }
#=> ['John', 'Mary']
As you can see, .each returns the array itself.
.map will return the array generated by executing the block.

Or use pluck and avoid creating the ruby objects
def tenant
Applicant.where(id: tenant_id).pluck(:first_name)
end
BTW - I see you have a tenant_id, if that means you have a belongs_to :tenant on the Billing class, you will want to pick a different method name (maybe "tenant_first_names"). If this is the case, and tenant has_many :applicants you can do this:
def tenant_first_names
tenant.applicants.pluck(:first_name)
end

Related

Is possible to append a key value to an ActiveRecord object

I want to have an object that has an associated value added to it.
This is what I am attempting to do:
#users = #search.results
#user_results = []
#users.each do |user|
#user_results = #user_results.push user << {photo_url: UsersPhoto.find_by_user_id(user.id).image_name}
end
I'm getting:
NoMethodError (undefined method `<<' for #):
Is there a way to do something like this?
This is what associations are for. You should just add an association to your User for the UsersPhoto you're trying to find, and use #user.users_photo.image_name.
class User < ActiveRecord::Base
has_one :users_photo
end
Failing that, add a photo_url method to your User model, which wraps up the UsersPhoto.find...:
class User < ActiveRecord::Base
def photo_url
UsersPhoto.find_by_user_id(id).image_name
end
end
Failing that, you can do what you're trying to do, but you'll need to add a attr_accessor :photo_url to your User model, and your syntax is all wrong:
class User < ActiveRecord::Base
attr_accessor :photo_url
end
#users.each do |user|
user.photo_url = UsersPhoto.find_by_user_id(user.id).image_name
end
Why don't you use the .pluck method?
#users = #search.results
#user_results = UsersPhoto.where(user_id: #users.pluck(:id) ).select(:image_name)
To explain, at least from what I am understanding, you want an array of image_names for the results #users. So just get their ids, find those UsersPhotos that match those ids and just get the image_name column

Access attributes inside hashed column

I have the following class:
class Profile < ActiveRecord::Base
serialize :data
end
Profile has a single column data that holds a serialized hash. I would like to define accessors into that hash such that I can execute profile.name instead of profile.data['name']. Is that possible in Rails?
The simple straightforward way:
class Profile < ActiveRecord::Base
serialize :data
def name
self.data['name']
end
def some_other_attribute
self.data['some_other_attribute']
end
end
You can see how that can quickly become cumbersome if you have lots of attributes within the data hash that you want to access.
So here's a more dynamic way to do it and it would work for any such top level attribute you want to access within data:
class Profile < ActiveRecord::Base
serialize :data
def method_missing(attribute, *args, &block)
return super unless self.data.key? attribute
self.data.fetch(attribute)
end
# good practice to extend respond_to? when using method_missing
def respond_to?(attribute, include_private = false)
super || self.data.key?(attribute)
end
end
With the latter approach you can just define method_missing and then call any attribute on #profile that is a key within data. So calling #profile.name would go through method_missing and grab the value from self.data['name']. This will work for whatever keys are present in self.data. Hope that helps.
Further reading:
http://www.trottercashion.com/2011/02/08/rubys-define_method-method_missing-and-instance_eval.html
http://technicalpickles.com/posts/using-method_missing-and-respond_to-to-create-dynamic-methods/
class Profile < ActiveRecord::Base
serialize :data # always a hash or nil
def name
data[:name] if data
end
end
I'm going to answer my own question. It looks like ActiveRecord::Store is what I want:
http://api.rubyonrails.org/classes/ActiveRecord/Store.html
So my class would become:
class Profile < ActiveRecord::Base
store :data, accessors: [:name], coder: JSON
end
I'm sure everyone else's solutions work just fine, but this is so clean.
class Profile < ActiveRecord::Base
serialize :data # always a hash or nil
["name", "attr2", "attr3"].each do |method|
define_method(method) do
data[method.to_sym] if data
end
end
end
Ruby is extremely flexible and your model is just a Ruby Class. Define the "accessor" method you want and the output you desire.
class Profile < ActiveRecord::Base
serialize :data
def name
data['name'] if data
end
end
However, that approach is going to lead to a lot of repeated code. Ruby's metaprogramming features can help you solve that problem.
If every profile contains the same data structure you can use define_method
[:name, :age, :location, :email].each do |method|
define_method method do
data[method] if data
end
end
If the profile contains unique information you can use method_missing to attempt to look into the hash.
def method_missing(method, *args, &block)
if data && data.has_key?(method)
data[method]
else
super
end
end

How to update instance variable in Rails model?

In my Rails app I have users who can have many payments.
class User < ActiveRecord::Base
has_many :invoices
has_many :payments
def year_ranges
...
end
def quarter_ranges
...
end
def month_ranges
...
end
def revenue_between(range, kind)
payments.sum_within_range(range, kind)
end
end
class Invoice < ActiveRecord::Base
belongs_to :user
has_many :items
has_many :payments
...
end
class Payment < ActiveRecord::Base
belongs_to :user
belongs_to :invoice
def net_amount
invoice.subtotal * percent_of_invoice_total / 100
end
def taxable_amount
invoice.total_tax * percent_of_invoice_total / 100
end
def gross_amount
invoice.total * percent_of_invoice_total / 100
end
def self.chart_data(ranges, unit)
ranges.map do |r| {
:range => range_label(r, unit),
:gross_revenue => sum_within_range(r, :gross),
:taxable_revenue => sum_within_range(r, :taxable),
:net_revenue => sum_within_range(r, :net) }
end
end
def self.sum_within_range(range, kind)
#sum ||= includes(:invoice => :items)
#sum.select { |x| range.cover? x.date }.sum(&:"#{kind}_amount")
end
end
In my dashboard view I am listing the total payments for the ranges depending on the GET parameter that the user picked. The user can pick either years, quarters, or months.
class DashboardController < ApplicationController
def show
if %w[year quarter month].include?(params[:by])
#unit = params[:by]
else
#unit = 'year'
end
#ranges = #user.send("#{#unit}_ranges")
#paginated_ranges = #ranges.paginate(:page => params[:page], :per_page => 10)
#title = "All your payments"
end
end
The use of the instance variable (#sum) greatly reduced the number of SQL queries here because the database won't get hit for the same queries over and over again.
The problem is, however, that when a user creates, deletes or changes one of his payments, this is not reflected in the #sum instance variable. So how can I reset it? Or is there a better solution to this?
Thanks for any help.
This is incidental to your question, but don't use #select with a block.
What you're doing is selecting all payments, and then filtering the relation as an array. Use Arel to overcome this :
scope :within_range, ->(range){ where date: range }
This will build an SQL BETWEEN statement. Using #sum on the resulting relation will build an SQL SUM() statement, which is probably more efficient than loading all the records.
Instead of storing the association as an instance variable of the Class Payment, store it as an instance variable of a user (I know it sounds confusing, I have tried to explain below)
class User < ActiveRecord::Base
has_many :payments
def revenue_between(range)
#payments_with_invoices ||= payments.includes(:invoice => :items).all
# #payments_with_invoices is an array now so cannot use Payment's class method on it
#payments_with_invoices.select { |x| range.cover? x.date }.sum(&:total)
end
end
When you defined #sum in a class method (class methods are denoted by self.) it became an instance variable of Class Payment. That means you can potentially access it as Payment.sum. So this has nothing to do with a particular user and his/her payments. #sum is now an attribute of the class Payment and Rails would cache it the same way it caches the method definitions of a class.
Once #sum is initialized, it will stay the same, as you noticed, even after user creates new payment or if a different user logs in for that matter! It will change when the app is restarted.
However, if you define #payments_with_invoiceslike I show above, it becomes an attribute of a particular instance of User or in other words instance level instance variable. That means you can potentially access it as some_user.payments_with_invoices. Since an app can have many users these are not persisted in Rails memory across requests. So whenever the user instance changes its attributes are loaded again.
So if the user creates more payments the #payments_with_invoices variable would be refreshed since the user instance is re-initialized.
Maybe you could do it with observers:
# payment.rb
def self.cached_sum(force=false)
if #sum.blank? || force
#sum = includes(:invoice => :items)
end
#sum
end
def self.sum_within_range(range)
#sum = cached_sum
#sum.select { |x| range.cover? x.date }.sum(&total)
end
#payment_observer.rb
class PaymentObserver < ActiveRecord::Observer
# force #sum updating
def after_save(comment)
Payment.cached_sum(true)
end
def after_destroy(comment)
Payment.cached_sum(true)
end
end
You could find more about observers at http://apidock.com/rails/v3.2.13/ActiveRecord/Observer
Well your #sum is basically a cache of the values you need. Like any cache, you need to invalidate it if something happens to the values involved.
You could use after_save or after_create filters to call a function which sets #sum = nil. It may also be useful to also save the range your cache is covering and decide the invalidation by the date of the new or changed payment.
class Payment < ActiveRecord::Base
belongs_to :user
after_save :invalidate_cache
def self.sum_within_range(range)
#cached_range = range
#sum ||= includes(:invoice => :items)
#sum.select { |x| range.cover? x.date }.sum(&total)
end
def self.invalidate_cache
#sum = nil if #cached_range.includes?(payment_date)
end

Rails throwing error on save (arguments 1 for 0) when saving through has_many

Due to the nature of my use of 'updated_at' (specifically for use in atom feeds), I need to avoid updating the updated_at field when a record is saved without any changes. To accomplish that I read up and ended up with the following:
module ActiveRecord
class Base
before_validation :clear_empty_strings
# Do not actually save the model if no changes have occurred.
# Specifically this prevents updated_at from being changed
# when the user saves the item without actually doing anything.
# This especially helps when synchronizing models between apps.
def save
if changed?
super
else
class << self
def record_timestamps; false; end
end
super
class << self
remove_method :record_timestamps
end
end
end
# Strips and nils strings when necessary
def clear_empty_strings
attributes.each do |column, value|
if self[column].is_a?(String)
self[column].strip.present? || self[column] = nil
end
end
end
end
end
This works fine on all my models except for my Email model. An Email can have many Outboxes. An outbox is basically a two-column model that holds a subscriber (email To:) and an email (email to send to subscriber). When I update the attributes of an outbox and then save Email, I get the (arguments 1 for 0) error on save (it points to the 'super' call in the save method).
Email.rb
has_many :outboxes, :order => "subscriber_id", :autosave => true
Outbox.rb
belongs_to :email, :inverse_of => :outboxes
belongs_to :subscriber, :inverse_of => :outboxes
validates_presence_of :subscriber_id, :email_id
attr_accessible :subscriber_id, :email_id
UPDATE: I also noticed that the 'changed' array isn't being populated when I change the associated models.
#email.outboxes.each do |out|
logger.info "Was: #{ out.paused }, now: #{ !free }"
out.paused = !free
end unless #email.outboxes.empty?
#email.save # Upon saving, the changed? method returns false...it should be true
...sigh. After spending countless hours trying to find a solution I came across this. Has I known that the 'save' method actually takes an argument I would have figured this out sooner. Apparently looking at the source didn't help in that regard. All I had to do was add an args={} parameter in the save method and pass it to 'super' and everything is working now. Unmodified records are saved without updating the timestamp, modified records are saved with the timestamp and associations are saved without error.
module ActiveRecord
class Base
before_validation :clear_empty_strings
# Do not actually save the model if no changes have occurred.
# Specifically this prevents updated_at from being changed
# when the user saves the item without actually doing anything.
# This especially helps when synchronizing models between apps.
def save(args={})
if changed?
super args
else
class << self
def record_timestamps; false; end
end
super args
class << self
remove_method :record_timestamps
end
end
end
# Strips and nils strings when necessary
def clear_empty_strings
attributes.each do |column, value|
if self[column].is_a?(String)
self[column].strip.present? || self[column] = nil
end
end
end
end

Rails has_many association changes into parent model dont reflect?

For example, i have this:
class Family < ActiveRecord::Base
:has_many :members
def aging(date)
members.find_all_by_birthday(date).each do |m|
m.age = m.age+1
# i dont want to put a m.save here
end
end
# some validations
end
#family = Family.first
#family.aging("2012-01-04")
#family.members.each do |m|
puts m.age
end
# it still the old age
I would like to use #family.save after calling aging method but it seems to not work that way, i would like to save this only if all validations met. This is just an example to simplify what i need
The members.find_all_by_birthday(date) does a separate query to return you a collection of members rather than fetching all members for the family into the association and then reducing it to the ones that have the corresponding birthday.
You can do:
members.select { |m| m.birthday == date }.each do |m|
m.age = m.age + 1
end
Which will modify the members in place. It has the disadvantage of fetching them all from the database which the find_all_by_birthday doesn't as it just fetches the ones you want.

Resources