Change format of attribute in serializer - ruby-on-rails-5

I'm using PostGIS ActiveRecord Adapter for GIS operations and types on PostgreSQL. In table elements I have attribute :coordinates type geometry. In ruby I can do coordinates.x and it returns x coordinate. But how can I parse this in serializer?
class ElementSerializer < ActiveModel::Serializer
attributes :coordinates
end
In development returns:
"POINT (45.815927 16.00538)"
In production returns:
0101000000581CCEFC6AE8464003E962D34A013040
I want something like:
class ElementSerializer < ActiveModel::Serializer
attributes {:coordinates => [:coordinates.x, :coordinates.y]}
end
Want to return:
[45.815927 16.00538]

I don't know why in development environment the serializer returns something different than in production, but when it comes to your main issue you can execute code like coordinates.x inside ActiveModelSerializer - you just need to call it through object instance variable, so something like code below should work in your case:
class ElementSerializer < ActiveModel::Serializer
attributes :coordinates
def coordinates
[object.coordinates.x, object.coordinates.y]
end
end

Related

In a Rails ActiveRecord model, is using after_initialize callbacks a very bad idea?

Let's suppose we have this model
class Account < ActiveRecord::Base
after_initialize :set_name
def set_name
self.name = ‘My Account’
end
end
Now I want run a query that returns only some attributes of the model but not all of them, in particular is not returning the "name" attribute that it is used in after_initialize callback
Account.group(:name).select("count(*), id").first
And then this execution raises the following error because the set_name callback uses an attribute that has not been "loaded" or selected into the records returned by the query.
ActiveModel::MissingAttributeError: missing attribute: name
Fortunately for some particular cases I can execute the same sql query without using the Account model at all to get the desired result
sql = Account.group(:name).select("count(*), id").to_sql
ActiveRecord::Base.connection.execute(sql).first
=> #<Mysql2::Result:0x00000106eddbc0>
But the point is, what if I want to get Account objects instead of a Mysql2::Result one? Should the .select method return "complete" objects with all their attributes (e.g. filling the missing columns with Nil's)? Or is just a very bad idea to use after_initialize callbacks for our ActiveRecord models? Of course we can also add some code in the callback to check if the property exists or not but, in my opinion, this is unnatural or sounds weird working in an OO language.
Most uses of after_initialize can be (and SHOULD be) replaced with defaults on the corresponding database columns. If you're setting the property to a constant value, you may want to look into this as an alternative.
EDIT: if the value isn't constant, a call to has_attribute?(:name) will guard against this error - ActiveModel::MissingAttributeError occurs after deploying and then goes away after a while
No, it is not a bad idea, in fact I use it very often at work. The valid use case for this would be when you want code to run before you try and do anything with the object. Here is a breakdown of some of the filters offered.
# Before you intend to do anything with the object
after_initialize
# Before you intend to save the object
before_save
# After you've saved the object
after_save
# Before you save a new record
before_create
# After you create a new object
after_create

I18n for attribute errors

Does exist a way to do something like this:
Model.human_attribute_name :attr
but with errors?
Something like this...
Model.human_error_name :attr, :error
For example:
Ticket.human_error_name :ticket_type_id, :no_tickets_left
I want to avoid this on my views:
I18n.t("activerecord.errors.models.ticket.attributes.ticket_type_id.no_tickets_left")
Not that I know of, but it is very easy to add such a method to all your models:
module Hemju
module HumanErrorName
extend ActiveSupport::Concern
def human_error_name(attr, error)
I18n.t("activerecord.errors.models.#{self.class.to_s}.attributes.#{attr}.#{error}")
end
end
end
ActiveRecord::Base.send(:include, Hemju::HumanErrorName)
Activating the extension in initializer:
require 'hemju/human_error_name'
Now every model has a 'human_error_name' method. Remember, every time you change the method, you have to restart the server.

Scope staticly reference another model resulting table not found

I use in-memory database for testing. The schema is reloaded in every test.
Recently my rspec complains that a table is not found. The reason is that a scope is referencing another model at load time.
class Item
scope :public, where(:store_id => Store.public_store_ids())
class Store
def self.public_store_ids
self.public.pluck(:id)
The problem is that, during the initializing when item model is loaded in the memory, the schema for store table has not been loaded yet, but my scope will try to query the public store ids, which results in the "table not found" error.
How can I make my item scope to evaluate dynamically at runtime? I didn't want to use join because it can slow down my query, but would it be my only way?
I realized that I can just make it a class method so it is evaluated at run time
def self.public
store_ids = BeautyStreet::Store.public_store_ids()
where(:store_id => store_ids)
end

Querying another model from inside a model created by FactoryGirl

Just starting with FactoryGirl. I have a Model named Subscription. It has a method 'set_price` which apparently does some calculations. In order to do so, it has to ask another model for some values:
def set_price
base_price = Option.find_by_key(:base_price).value.to_f
# […] some calculations
end
When running my specs I get:
NoMethodError:
undefined method `value' for nil:NilClass
Which is quite logical since I didn't (yet?) create any Options.
Is FactoryGirl suited for this? Do I have to create Option fixtures in this case? Or just mock it?
This will fail because there are no Options in the database. You can either create the option factory before calling set_price in the test (you'll need to make sure find_by_key(:base_price) will return your factory created option in this case), or you can as you say use a mock:
option = mock_model('Option', :value => 1)
Option.stub(:find_by_key).and_return(option)
The mock has the advantage that it will not touch the database, but it's potentially more brittle.

Rails 3 - how do you use methods like where and sum on proxy_targets in association extension methods?

in my app, 2 peope can be friends via a friendships model and they can create records of how much money they lent to the other friend.
I want to create something like this
#friendship.receipts.balance_for(#user) which will return how much #user owes to or is owed by their friend in this friendship. i.e. if it returns -50 they owe $50 or if its positive they are owed $50
Id also like to be able further scope this for use in live filtering (by dates, tags etc) i.e. would like this
#friendship.receipts.where(:tag => "groceries).balance_for(#user)
My first attempt was to create an association extension on the friendship model
class Friendship < ...
has_many :receipts do
def balance_for(user)
user_total_spent = proxy_target.sum(:value, :payer_id => user.id)
friend_total_spent = proxy_target.sum(:value, :payer_id => friend.id)
return user_balance = user_total_spent - friend_total_spent
end
end
end
but unfortunately the method sum cant be used on proxy_target because it returns a plain array and not the Activerecord::Relation class sum expects.
Whats a good workaround this??? Id like to create a method that can be called on scoped results.
You might want to try simply calling sum without an explicit receiver. I'm looking for the solution to a similar problem, and found what looks like the answer to your question here: http://withoutscope.com/2008/8/22/don-t-use-proxy_target-in-ar-association-extensions
Unfortunately, it doesn't solve my problem. I'm trying to figure out how to extend models retrieved through my association extension with a module where the module name is a member of the proxy's owner.