Virtual attributes for embedded model - ruby-on-rails

is it possible to have virtual attributes for embedded models?
I have a class A:
class A
include Mongoid::Document
field :name, type: String
embedded_in :b
def pnl=(p)
end
def pnl
"p"
end
def as_json(options={})
attrs = super(methods: [:pnl])
attrs
end
end
and class B:
class B
include Mongoid::Document
field :name, type: String
embedds :a
end
If I take a record from B:
b = B.first
b.as_json
I dont get the virtual attributes from embedded class a - I only see the persistent attributes. My controller is supposed to return b inclusive all virtual attributes of their embedded objects - but this doesnt work.
If I do:
b.a.as_json
Then I get the virtual attributes of embedded class A but this doesnt help. If I do not use embedded but referenced relationship (belongs_to/ has_one) it works all fine, but I thought embedded relationship would be better for my use case.
CORRECTION: It also doesnt work with referenced relationship. It seems virtual attributes are not supported for any relationship?
Is there a way I also get the virtual attributes calling b.as_json?
Thanks,
Michael

seems as_json doesnt recursivle call as_json. The solution is to overwrite serializable_hash instead of as_json and it works as expected. Not sure what is the reason it doesnt work with as_json.
Rgds
Michaek

Related

How to add a virtual attribute to a model in Ruby on Rails?

I'm working on a RubyonRails/ActiveAdmin application. My RoR version is 4.2.5 and AA version is 1.0.0. I have a model Message as follows.
class Message < ActiveRecord::Base
belongs_to :user
validates :user, :content, presence: true
def palindrome
# return true/false
end
end
As you see, I want to have a read-only attribute palindrome which only depends on the content of message. I want this attribute to be treated exactly like a normal attribute. By normal, I mean when I retrieve messages via rails console or request json format of messages, I want to see a palindrome attribute in the list. I would also like to have a filter for message by this attribute.
I'm not sure how could I achieve this.
Ruby actually lets you create virtual attributes this way, which keeps you from having to manually create getter and setter methods:
attr_reader :palindrome #getter
attr_writer :palindrome #setter
attr_accessor :palindrome #both
You can also pass multiple arguments too:
attr_accessor :palindrome, :foo, :bar
The documentation for it isn't the greatest.
In your model, you can write attribute accessors (reader/writer) for your virtual attribute palindrome attribute this way:
# attr_reader
def palindrome
self[:palindrome]
end
# attr_writer
def palindrome=(val)
self[:palindrome] = val
end
# virtual attribute
def palindrome
#return true/false
end
And, as you are using Rails 4, you have to whitelist palindrome attribute like any other model attribute in your strong param definition inside your controller in order to able to mass assign the value of palindrome. Something like this:
# your_controller.rb
private
def your_model_params
params.require(:message).permit(:palindrome)
end
Take a look at this RailsCast on Virtual Attributes. Although, it's a bit old, but would be useful for concepts.
Note:
A virtual attribute will not show up in the param list automatically. But, you should be able to access it via Rails console like this: Message.new.palindrome. Also, you can expose this virtual attribute in your JSON API, for example if you are using Active Model Serializer, you can have: attribute palindrome in your MessageSerializer and then palindrome will be exposed to the JSON API.
Since Rails 5 you can also set virtual attributes like this:
attribute :palindrome, :boolean
It automatically casts the attribute to the specified type, which can be useful when the value comes from forms. This GoRails video shows some really good examples of both using the attr_accessor and the attribute approach. The documentation also includes some examples.

Ruby copying between active model objects

Simply trying to work out how to copy attributes from one Active Model to another without having to do it one by one.
I have two models one is RFM (ruby to filemaker) one is mongoid both mixin active model.
gem "ginjo-rfm"
the model
require 'rfm'
class StudentAdmin < Rfm::Base
config :layout => 'STUDENT_ADMIN_LAYOUT'
attr_accessor :name_first,:name_last
end
Mongoid model
class Student
include Mongoid::Document
field :first_name, type: String
field :last_name, type: String
end
Is there a quicky copy I can do? I found a sample between active record objects e.g.
student_admin = ... #load StudentAdmin
Student.new(student_admin.attributes.slice(Student.attribute_names))
but RFM doesn't provide a attributes method.
EDIT
Sorry what I am trying to achive is a better way than this
student_admins = #get student admins from external service
students = []
student_admins.each() do |sa|
students.push(Student.create!(first_name: sa.name_first, last_name: sa.name_last))
end
This example only shows 2 attributes, but in practice there is over 50 and was wondering if there is a way to do it without having to specify every attribute e.g. if the attribute names are the same on two objects copy them automatically.
Try this:
students = student_admins.map do |sa|
attrs = sa.methods.inject({}) do |hash, m|
next unless Student.column_names.include? m.to_s
hash[m] = sa.send m
end
Student.create(attrs)
end
Student would have to be a class that inherits from ActiveRecord::Base:
class Student < ActiveRecord::Base
...
end

Parent fields are all nil when accessed by child (embedded 1-n)

I have a 1-n relationship defined as follows:
class User
field :email, type: String
embeds_many :papers
end
class Paper
embedded_in :user
end
If I try and access the parent fields (user) from the child (paper) like so:
User.all.map(:papers).flatten.first.user.email
Then I get nil :(
Accessing like this works fine though:
User.all.first.papers.first.user.email
It's a mispelling lire report on comment. To call a method on a map,you need use & before your symbol.
Try:
User.all.map(&:papers).flatten.first.user.email

Dynamically determining Rails model attributes

I have a model (simplified version below - the full one has a lot more fields) on which I want to do a search.
class Media < ActiveRecord::Base
attr_accessible :subject, :title, :ref_code
validates :title, :presence => true
validates :subject, :presence => true
def self.search(terms_hash)
return if terms_hash.blank?
composed_scope = self.scoped
terms_hash.each_pair do |key, value|
if self.respond_to? key and not value.is_blank?
value.split(' ').each do |term|
term = "%#{term}%"
composed_scope = composed_scope.where("#{key} LIKE ?", term)
end
end
composed_scope
end
end
Since the advanced search form is almost identical to the form used to create/update instances of the model, I want to create a method that dynamically looks through the params list of the request and matches form fields to model attributes via the name. So if the search request was,
/search?utf8=✓&ref_code=111&title=test&subject=code&commit=Search
then my controller could just call Media.search(params) and it would return a scope that would search the appropriate fields for the appropriate value(s). As you can see I tried using respond_to? but that is only defined for instances of the model rather than the actual model class and I need something that ignores any params not related to the model (eg. utf8 and commit in this case). If this all works well, I plan to refactor the code so that I can reuse the same method for multiple models.
Is there a class method similar to respond_to?? How would you go about the same task?
I know this is related to get model attribute dynamically in rails 3 but it doesn't really answer what I'm trying to do because I want to do it on the the model rather than an instance of the model.
There is a columns method on ActiveRecord model classes so you can easily get a list of attribute names:
names = Model.columns.map(&:name)
You could remove a few common ones easily enough:
names = Model.columns.map(&:name) - %w[id created_at updated_at]
The accessible_attributes class method might also be of interest, that will tell you what has been given to attr_accessible.

mongo mapper with STI with more than one type?

I have a series of models all which inherit from a base model Properties
For example Bars, Restaurants, Cafes, etc.
class Property
include MongoMapper::Document
key :name, String
key :_type, String
end
class Bar < Property
What I'm wondering is what to do with the case when a record happens to be both a Bar & a Restaurant? Is there a way for a single object to inherit the attributes of both models? And how would it work with the key :_type?
I think you want a module here.
class Property
include MongoMapper::Document
key :name, String
key :_type, String
end
module Restaurant
def serve_food
puts 'Yum!'
end
end
class Bar < Property
include Restaurant
end
Bar.new.serve_food # => Yum!
This way you can let many models have the properties of a restaurant, without duplicating your code.
What you could also try, though I haven't experimented with it myself, is multiple levels of inheritance. e.g.:
class Property
include MongoMapper::Document
key :name, String
key :_type, String
end
class Restaurant < Property
key :food_menu, Hash
end
class Bar < Restaurant
key :drinks_menu, Hash
end
Not sure off the top of my head whether MongoMapper supports this, but I don't see why it wouldn't.

Resources