How can I tell whether a model is embedded in Mongoid? - ruby-on-rails

So I have two models:
#app/models/rate.rb
class Rate
include Mongoid::Document
embeds_many :tiers
field :name
# other fields...
end
#app/models/tier.rb
class Tier
include Mongoid::Document
embedded_in :rate
field :name
# other fields...
end
Now according to the mongoid documentation I can do the following to figure out whether a model is embedded in another model:
rate.tiers.embedded?
=> true
Tier.reflect_on_association(:rate).macro
=> :embedded_in
But for both these approaches I need to know that Tiers are embedded in Rates. Is there a way that I can find out whether Tiers are an embedded model and then find out what model they are embedded in without knowing its relationship with Rates beforehand?

You could use reflect_on_all_associations which:
Returns all relation metadata for the supplied macros.
So if you say:
embedded_ins = M.reflect_on_all_associations(:embedded_in)
you'll get an array (possibly empty) of Mongoid::Relations::Metadata instances in embedded_ins. Then you can say:
embedded_ins.first.class_name
to get the name of the class we're embedded in.

I had hoped to find the answer here http://mongoid.org/en/mongoid/docs/relations.html but unfortunately not all the methods created in regards to relations were documented there. Here is how to do it:
# To get whether a class is embedded
Model.embedded?
# So in the case of Tiers I would do
Tier.embedded?
=> true
# To get the class that the object is embedded in
# Note, this assumes that the class is only embedded in one class and not polymorphic
klass = nil
object.relations.each do |k, v|
if v.macro == 'embedded_in'
klass = v.class_name
end
end
# So in our case of Tiers and Rates I would do
tier = Tier.find(id)
if tier.embedded?
rate = nil
tier.relations.each do |k, v|
if v.macro == 'embedded_in'
rate = v.class_name # Now rate is equal to Rate
end
end
rate.find(rate_id) # This will return the rate object with id = rate_id
end

Related

How to handle rails enums?

How can I handle enums in rails? I have googled this, but not finding any clean solutions. So far I have come up with including the concern below on models that use interaction_type_id. This is my foreign key to my enum table in the database. Using this approach I don't have to use ids all over my code, but when saving an object that relates to an interact_type I can say
myobject.interaction_type = :file_download
This can then persist the the database with the correct id since the concern(see concern below - included on models that use the enum) will return the correct id.
module InteractionTypeEnum
extend ActiveSupport::Concern
included do
INTERACTION_TYPE = { file_download: 1, email: 2, telesales: 3, registration: 4, enrolment: 5 }
end
def interaction_type
INTERACTION_TYPE.key(read_attribute(:interaction_type_id)).to_s.gsub('_',' ').capitalize
end
def interaction_type=(s)
write_attribute(:interaction_type_id, INTERACTION_TYPE[s])
end
end
This just feels heavy. There must be an easier/cleaner way. Now when trying to write tests for this it gets even more messy.
Most of the reasons for wanting my enums in code and database are performance (code) and reporting (database).
Any help appreciated. Thanks.
I recommend the active_enum gem.
Example from their docs, if you have an integer column called sex on the class User:
class User < ActiveRecord::Base
enumerate :sex do
value :name => 'Male'
value :name => 'Female'
end
end
Or you can define the enum in a seperate class:
class Sex < ActiveEnum::Base
value 1 => 'Male'
value 2 => 'Female'
end
class User < ActiveRecord::Base
enumerate :sex, :with => Sex
end
I like the abstraction it provides, and it saves you from having to create an entire database table just to store your enum values.
I use the following method, say I have
class PersonInfo < ActiveRecord::Base
belongs_to :person_info_type
end
and PersonInfoType is a simple domain table, containing the possible types of information.
Then I code my model as follows:
class PersonInfoType < ActiveRecord::Base
PHONE = 1
EMAIL = 2
URL = 3
end
I have a seed fills the database with the corresponding data.
And so when assigning some person-information can do something like
person.person_infos << PersonInfo.create(:info => 'http://www.some-url.com', :person_info_type_id => PersonInfoType::URL)
This code can then be further cleaned up using relations:
class PersonInfo
belongs_to :person_info_type
def self.phones
PersonInfo.where(:person_info_type_id => PersonInfoType::PHONE)
end
end
person.person_infos << PersonInfo.phones.create(:info => '555 12345')

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

Mongoid: Querying from two collections and sorting by date

I currently have the following controller method in a Rails app:
def index
#entries = []
#entries << QuickPost.where(:user_id.in => current_user.followees.map(&:ff_id) << current_user.id)
#entries << Infographic.where(:user_id.in => current_user.followees.map(&:ff_id) << current_user.id)
#entries.flatten!.sort!{ |a,b| b.created_at <=> a.created_at }
#entries = Kaminari.paginate_array(#entries).page(params[:page]).per(10)
end
I realise this is terribly inefficient so I'm looking for a better way to achieve the same goal but I'm new to MongoDB and wondering what the best solution would be.
Is there a way to make a sorted limit() query or a MapReduce function in MongoDB across two collections? I'm guessing there isn't but it would certainly save a lot of effort in this case!
I'm currently thinking I have two options:
Create a master 'StreamEntry' type model and have both Infographic and QuickPost inherit from that so that both data types are stored on the same collection. The issue with this is that I have existing data and I don't know how to move it from the old collections to the new.
Create a separate Stream/ActivityStream model using something like Streama (https://github.com/christospappas/streama). The issues I can see here is that it would require a fair bit of upfront work and due to privacy settings and editing/removal of items the stream would need to be rebuilt often.
Are there options I have overlooked? Am I over-engineering with the above options? What sort of best practices are there for this type of situation?
Any info would be greatly appreciated, I'm really liking MongoDB so far and want to avoid falling into pitfalls like this in the future. Thanks.
The inherit solution is fine, but when the inherited models are close.
For example :
class Post < BasePost
field :body, type: String
end
class QuickPost < BasePost
end
class BasePost
field :title, type: String
field :created_at, type: Time
end
But when the models grows, or are too different, your second solution is better.
class Activity
include Mongoid::Document
paginates_per 20
field :occurred_at, :type => Time, :default => nil
validates_presence_of :occurred_at
belongs_to :user
belongs_to :quick_post
belongs_to :infographic
default_scope desc(:occurred_at)
end
and for example :
class QuickPost
include Mongoid::Document
has_one :activity, :dependent => :destroy
end
The dependant destroy make the activity destroyed when the QuickPost is destroyed. You can use has_many and adapt.
And to create the activities, you can create an observer :
class ActivityObserver < Mongoid::Observer
observe :quick_post, :infographic
def after_save(record)
if record.is_a? QuickPost
if record.new_record?
activity = record.build_activity
activity.user = record.user
# stuff when it is new
else
activity = record.activity
end
activity.occurred_at = record.occurred_at
# common stuff
activity.save
end
end
end

Validating polymorphic association type in Rails

I am developing a controller that creates a model with a polymorphic belongs_to association. What I do now to find the model it belongs to is as follows:
def find_polymorphic_model(classes)
classes_names = classes.map { |c| c.name.underscore + '_id' }
params.select { |k, v| classes_names.include?(k) }.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
raise InvalidPolymorphicType
end
Where classes is an array of valid types for the association.
The problem with this approach is that I have to remember in the controller which types are allowed for the model I am creating.
Is there a way to find which types are allowed for a certain polymorphic belongs_to association? Or maybe I am doing this wrong and I should not let a polymorphic controller be exposed without nesting it in the polymorphic resource (in the router)?
I also think there may be problems with the fact that Rails lazy loads classes, so to be able to find out this thing I would have to explicitly load all models at initialization time.
For your validation you don't have to get all the possible polymorphic types. All you need is to check if the specified type (say, the value of taggable_type attribute) is suitable. You can do it this way:
# put it in the only_polymorphic_validator.rb. I guess under app/validators/. But it's up to you.
class OnlyPolymorphicValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
polymorphic_type = attribute.to_s.sub('_type', '').to_sym
specified_class = value.constantize rescue nil
this_association = record.class.to_s.underscore.pluralize.to_sym
unless(specified_class.reflect_on_association(this_association).options[:as] == polymorphic_type rescue false)
record.errors[attribute] << (options[:message] || "isn't polymorphic type")
end
end
end
And then use:
validates :taggable_type, only_polymorphic: true
to check whether :taggable_type contains a valid class.
Answered too soon and didn't get that you are looking at polymorphic associations.
For associations in general, use reflect_on_all_associations.
However, for some polymorphic associations, there is no way to know all the classes which can implement the association. For some others, you need to look at the type field.

Reassigning an ActiveRecord instance and corresponding foreign keys

In Rails/ActiveReocrd is there a way to replace one instance with another such that all the relations/foreign keys get resolved.
I could imagine something like this:
//setup
customer1 = Customer.find(1)
customer2 = Customer.find(2)
//this would be cool
customer1.replace_with(customer2)
supposing customer1 was badly configured and someone had gone and created customer2, not knowing about customer1 it would be nice to be able to quickly set everything to customer 2
So, also this would need to update any foreign keys as well
User belongs_to :customer
Website belongs_to :customer
then any Users/Websites with a foreign key customer_id = 1 would automatically get set to 2 by this 'replace_with' method
Does such a thing exist?
[I can imagine a hack involving Customer.reflect_on_all_associations(:has_many) etc]
Cheers,
J
Something like this could work, although there may be a more proper way:
Updated: Corrected a few errors in the associations example.
class MyModel < ActiveRecord::Base
...
# if needed, force logout / expire session in controller beforehand.
def replace_with (another_record)
# handles attributes and belongs_to associations
attribute_hash = another_record.attributes
attribute_hash.delete('id')
self.update_attributes!(attribute_hash)
### Begin association example, not complete.
# generic way of finding model constants
find_model_proc = Proc.new{ |x| x.to_s.singularize.camelize.constantize }
model_constant = find_model_proc.call(self.class.name)
# handle :has_one, :has_many associations
have_ones = model_constant.reflect_on_all_associations(:has_one).find_all{|i| !i.options.include?(:through)}
have_manys = model_constant.reflect_on_all_associations(:has_many).find_all{|i| !i.options.include?(:through)}
update_assoc_proc = Proc.new do |assoc, associated_record, id|
primary_key = assoc.primary_key_name.to_sym
attribs = associated_record.attributes
attribs[primary_key] = self.id
associated_record.update_attributes!(attribs)
end
have_ones.each do |assoc|
associated_record = self.send(assoc.name)
unless associated_record.nil?
update_assoc_proc.call(assoc, associated_record, self.id)
end
end
have_manys.each do |assoc|
associated_records = self.send(assoc.name)
associated_records.each do |associated_record|
update_assoc_proc.call(assoc, associated_record, self.id)
end
end
### End association example, not complete.
# and if desired..
# do not call :destroy if you have any associations set with :dependents => :destroy
another_record.destroy
end
...
end
I've included an example for how you could handle some associations, but overall this can become tricky.

Resources