What I'm trying to do is add an attribute to an activeresource resource that computer based on other attributes created from the servers response.
Further complicating things is that the attributes the computation depends on are part of a has_many association say has_many: items.
What I'd like to happen is when u = User.find(123) is called the items are retrieved and an attribute added to User based on some computation, for example u.blue_item_count.
The new attribute also need to appear when to object is serialized into XML or JSON. For instance u would serialize into {"id":1, "name":"bob", "blue_item_count":21 }.
Thanks
Just define a method on your User object that uses the items association. For example:
class User < ActiveRecord::Base
has_many :items
def blue_item_count
items.each do |item|
...
end
end
end
Related
I'm building an application where users are part of an Organisation. An organisation has many Lists, which in turn have many ListItems.
Now, I would like for admin users to be able to specify which attributes are available on list items, based on the organisation they belong to (or rather, on the organisation their list belongs to), without having to touch any code.
So far, when defining attributes that are not bound to a specific column in the database, I have used document_serializable, a nifty little gem (based on virtus) which serializes virtual attributes to a JSONB column in the db. I like this approach, because I get all of virtus' goodies (types, coercion, validations, etc.), and because data ends up sitting in a JSONB column, meaning it can be loaded quickly, indexed, and searched through with relative ease.
I would like to keep using this approach when adding these user-defined attributes on the fly. So I'd like to do something like:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
organisation.list_attributes.each do |a, t|
attribute a, t
end
end
Where Organisation#list_attributes returns the user-defined hash of attribute names and their associated types, which, for example, might look like:
{
name: String,
age: Integer
}
As you might have guessed, this does not work, because organisation.list_attributes.each actually runs in the context of ListItem, which is an instance of Class, and Class doesn't have an #organisation method. I hope that's worded in a way that makes sense1.
I've tried using after_initialize, but at that point in the object's lifecycle, #attribute is owned by ActiveRecord::AttributeMethods::Read and not DocumentSerializable::ClassMethods, so it's an entirely different method and I can't figure out wether I can still access the one I need, and wether that would even work.
Another alternative would be to find the organisation in question in some explicit way, Organisation#find-style, but I honestly don't know where I should store the information necessary to do so.
So, my question: at the moment of instantiating (initializing or loading2) a record, is there a way I can retrieve a hash stored in a database column of one of its relations? Or am I trying to build this in a completely misguided way, and if so, how else should I go about it?
1 To clarify, if I were to use the hash directly like so:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
{
name: String,
age: Integer
}.each do |a, t|
attribute a, t
end
end
it would work, my issue is solely with getting a record's relation at this earlier point in time.
2 My understanding is that Rails runs a model's code whenever a record of that type is created or loaded from the database, meaning the virtual attributes are defined anew every time this happens, which is why I'm asking how to do this in both cases.
at the moment of instantiating (initializing or loading) a record, is
there a way I can retrieve a hash stored in a database column of one
of its relations?
Yes. This is fairly trivial as long as your relations are setup correctly / simply. Lets say we have these three models:
class ListItem < ApplicationRecord
belongs_to :list
end
class List < ApplicationRecord
belongs_to :organisation
has_many :list_items
end
class Organisation < ApplicationRecord
has_many :lists
end
We can instantiate a ListItem and then retrieve data from anyone of its parents.
#list_item = ListItem.find(5) # assume that the proper inherited
foreign_keys exist for this and
its parent
#list = #list_item.list
#hash = #list.organisation.special_hash_of_org
And if we wanted to do this at every instance of a ListItem, we can use Active Record Callbacks like this:
class ListItem < ApplicationRecord
belongs_to :list
# this is called on ListItem.new and whenever we pull from our DB
after_initialize do |list_item|
puts "You have initialized a ListItem!"
list = list_item.list
hash = list.organisation.special_hash_of_org
end
end
But after_initialize feels like a strange usage for this kind of thing. Maybe a helper method would be a better option!
A model(FirstModel) in my rails app has a has_many relationship with another model(Item).
class FirstModel < ActiveRecord::Base
has_many :items, :dependent => :destroy
def item_array
a = []
self.items.each do |item|
a.push(item.item_thing)
end
a
end
end
Item itself belongs :item_thing through a polymorphic association. A method on FirstModel returns an array of "items", not the join model object(which is of class Item), but the item_thing of each Item object, and therefore it is an array of objects of different classes (checked in rails console, and the method returns the array just fine)
class Item < ActiveRecord::Base
belongs_to :item_thing, :polymorphic => true
end
In my show.json.jbuilder for FirstModel, I want to take that array of "items" and use different templates based on which class the item belongs to. The following file path is "views/api/items/show.jbuilder"
json.extract! #item, :name
json.items #item.item_array do |item|
if item.class == 'Book'
json.partial! 'api/books/show', item
end
if item.class == 'Car'
json.partial! 'api/cars/show', item
end
end
The car and book template paths are "views/api/cars/show.json.jbuilder" and "views/api/books/show.json.jbuilder.
When I get the json from the above template the name of the FirstModel object is visible, items array is empty, whereas I am trying to get an array of hashes for each item, rendered through its respective template.
Thank you for helping!
You can probaly just do something like the code below if you name your items and partials correctly.
json.items #item.item_array do |item|
json.partial! "api/#{item.class.to_s.pluralize.downcase}/show"
end
Alternatively you could use a case statement on item.class.to_s, or as one of the other answers suggested, use the class actual class instead of the class name as a string.
I think your code will work if you change your class strings to actual class names.
I.e. User and not "User".
Or you could keep the strings and use .class.to_s, which will return "User" for a user record.
If you're interested in metaprogramming, the constantize, camelify and underscore methods from activesupport might be useful down the line.
Context:
Each Order has many Items & Logistics. Each Item & Logistic (as well as the Order itself) have many Revenues.
I am creating Order + Items & Logistics at once using an accepts_nested_attributes_for on Order. However, Revenues gets created using an after_create callback on each of the models Order, Item, and Logistics. Why? Because given the difference in interpretation in these models, the code reads cleaner this way. (But if this way of doing it is what's causing this question to be asked, I will obviously reconsider!)
One key attribute that I need to store in Revenues is pp_charge_id. But pp_charge_id is not something that either Order, Items, or Logistics needs to worry about. I've attached an attr_accessor :pp_charge_id to Order, so that one works fine, however, once I'm in the child Items or Logistics models, I no longer have access to pp_charge_id which again I need to save an associated Revenue. How should I do this?
Controller Code:
#order = Order.new(params) #params includes Order params, and nested params for child Item & Logistics
#order.pp_charge_id = "cash"
#order.save #I need this to not only save the Order, the children Item & Logistics, but then to also create the associated Revenue for each of the aforementioned 3 models
ORDER Model Code:
has_many :items
has_many :revenues
attr_accessor :pp_charge_id
after_create :create_revenue
def create_revenue
self.revenues.create(pp_charge_id: self.pp_charge_id)
end
#This WORKS as expected because of the attr_accessor
ITEM/ LOGISTIC model code:
has_many :revenues
belongs_to :order
after_create :create_revenue
def create_revenue
self.revenues.create(pp_charge_id: self.order.pp_charge_id)
end
#This DOES NOT work because self.order.pp_charge_id is nil
ORDER model code:
belongs_to :order
belongs_to :item
belongs_to :logistic
Again I understand the attr_accessor is not designed to persist across a request or even if the Order itself is reloaded. But it also doesn't make sense to save it redundantly in a table that has no use for it. If the only way to do this is to put the pp_charge_id into the params for the order and save everything all at once (including Revenues), then let me know because I know how to do that. (Again, would just rather avoid that because of how it's interpreted: params are coming from User, Revenue data is something I'm providing)
I think if you want the order's pp_charge_id to apply to all its items and logistics, I'd put all that into the order's after_create callback:
# order.rb
def create_revenue
revenues.create(pp_charge_id: pp_charge_id)
items.each {|i| i.revenues.create(pp_charge_id: pp_charge_id)}
logistics.each {|l| l.revenues.create(pp_charge_id: pp_charge_id)}
end
EDIT: Alternately, you could add inverse_of to your belongs_to declarations, and then I believe Item#create_revenue would see the same Order instance that you set in the controller. So if you also added an attr_accessor to the Item class, you could write its create_revenue like this:
# item.rb
def create_revenue
revenues.create(pp_charge_id: pp_charge_id || order.pp_charge_id)
end
This should cover the new requirement you've mentioned in your comment.
instead of using after_create and accessors you should consider having a proper method that does exactly what you need, ie:
Order.create_with_charge(:cash, params)
i find it disturbing to persist redundant information in the database just because the code reads cleaner that way!
I have a couple of models that are composites of multiple objects. I basically manage them manually for saves and updates. However, when I select items out, I don't have access to the associated properties of said item. For example:
class ObjectConnection < ActiveRecord::Base
def self.get_three_by_location_id location_id
l=ObjectConnection.find_all_by_location_id(location_id).first(3)
r=[]
l.each_with_index do |value, key|
value[:engine_item]=Item.find(value.engine_id)
value[:chassis_item]=Item.find(value.chassis_id)
r << value
end
return r
end
end
and each item:
class Item < ActiveRecord::Base
has_many :assets, :as => :assetable, :dependent => :destroy
When I use the ObjectLocation.find_three_by_location_id, I don't have access to assets whereas if use Item.find(id) in most other situations, I do.
I tried using includes but that didn't seem to do it.
thx
Sounds like the simplest solution would be to add methods to your ObjectConnection model for easy access like so:
class ObjectConnection < ActiveRecord::Base
def engine
Engine.find(engine_id)
end
def chassis
Chassis.find(chassis_id)
end
# rest of class omitted...
I'm not exactly sure what you're asking... If this doesn't answer what you're asking, then can you try to be a little bit more clear with what exactly you are trying to accomplish? Are the Chassis and Engine mdoels supposed to be polymorphic associations with your Item model?
Also, the code you're using above won't work due to the fact that you are trying to dynamically set properties on a model. It's not your calls to Item.find that are failing, it's your calls to value[:engine_item]= and value[:chassis_item] that are failing. You would need to modify it to be something like this if you wanted to keep that flow:
def self.get_three_by_location_id location_id
l=ObjectConnection.find_all_by_location_id(location_id).first(3)
r=[]
l.each_with_index do |obj_conn, key|
# at this point, obj_conn is an ActiveRecord object class, you can't dynamically set attributes on it at this point
value = obj_conn.attributes # returns the attributes of the ObjectConnection as a hash where you can then add additional key/value pairs like on the next 2 lines
value[:engine_item]=Item.find(value.engine_id)
value[:chassis_item]=Item.find(value.chassis_id)
r << value
end
r
end
But I still think that this whole method seems unnecessary due to the fact that if you setup proper associations on your ObjectConnection model to begin with, then you don't need to go and try to handle the associations manually like you're attempting to do here.
I have two tables:
stores
raw_stores_data
The raw_stores_data is received from a third party daily.
I'd update certain fields of the stores model if those fields have been modified for that record in raw_stores_data.
Currently I have a bunch of conditional statements that check each of those fields. Is there any better way to code this?
new_data = raw_stores_data.all.select do |item|
item.store_id.present?
end
new_data.each do |item|
if item.field1 != item.stores.field1
...
...
...
# update record with hash of fields to update created above
end
You could add an association and special mutators to the 'raw' model that know how manipulate the 'stores' object. This serves to keep the model code in the model. Thin controller, comprehensive models, etc.
class Store < ActiveRecord::Base
has_one :raw_stores_data
end
class RawStoresData < ActiveRecord::Base
belongs_to :store
def field1=(value)
store.field1 = value
store.save!
field1 = value
end
end
I'm hand waving at some of the details, and you might want to reverse the direction of the association or make it go both directions.
EDIT:
You would use this as such:
raw_data = RawStoreData.find(param[:id]) # or new or however you get this object
raw_data.field1 = param[:field1]
The act of assigning will use the 'field1=' method, and make the change to the associated store object. If you're worried about saving unnecessarily, you could conditionalize in that method to only save if the value changed.
I hope this is clearer.