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
Related
I am trying to add has_one_attached and has_many_attached
dynamically to a model on each request. I am able to do it, but probably there is a bug in the implementation, and I am looking for a better/correct way.
Here is my current setup. I have a products table that belongs to a shop model. A shop defines the attributes that a product can have using the custom_fields model.
Here is some code.
CustomField Model
# This model has mainly two attributes field_name and field_type. The records in this table are dynamic i.e., user-generated
# Here are some examples of the kind of records that can be stored:
# *) An icon field_name with image field_type
# *) manual field_name with file field_type
# *) assets field_name with multi_file field_type
# *) photos field_name with multi_image field_type
# *) product_name field_name with text type
# *) price field_name with number type
class CustomField < ApplicationRecord
end
Shop Model
class Shop < ApplicationRecord
has_many :custom_fields
has_many :products
end
Product Model
# This model has a JSON column (field_data) to store non active_storage fields.
# For example fields like product_name and price information will be stored in the JSON column
class Product < ApplicationRecord
belongs_to :shop
end
ProductDecorator class. Instantiated on each request in a controller
class ProductDecorator < SimpleDelegator
def initialize(object)
super
#custom_fields = shop.custom_fields
init_fields
end
def init_fields
# Save the non active_storage attributes to the JSON column.
# Here I am adding the store_accessor to the singleton_class. On each request we have a different singleton_class
__getobj__.singleton_class.class_eval do
store_accessor :field_data, *simple_fields
end
# We can't use has_one_attached on a singleton_class class or at least I couldn't figure out a way.
# Here I am monkey patching the class to make it work on each request which needs to be avoided
has_one_attached_fields.each do |field|
__getobj__.class.class_eval do
has_one_attached field
end
end
# Same like above here are monkey patching the class on each request.
has_many_attached_fields.each do |field|
__getobj__.class.class_eval do
has_many_attached field
end
end
end
def simple_fields
non_scalar = %w[file image multi_file multi_image]
#custom_fields.reject { |f| non_scalar.include? f.field_type }.pluck(:field_name)
end
def has_one_attached_fields
#custom_fields.select { |f| %w[file image].include? f.field_type }.pluck(:field_name)
end
def has_many_attached_fields
#custom_fields.select { |f| %w[multi_file multi_image].include? f.field_type }.pluck(:field_name)
end
end
The product model is decorated with the ProductDecorator class to modify the Product behavior in a rails controller on each request.
Based on the custom_fields defined in the shop model, the decorator class adds the active_storage functionality to a model.
The current code will work but, the dynamically added functionality will live forever in the class and will not be cleaned up after the request as the methods are added to the class instead of a singleton class.
I looked into ruby refinements but couldn't come up with a solution yet. Another option that I have is to sidestep active_storage completely and build custom rails models but would like to use active_storage if possible.
Is there any better way? Any help in this will be greatly appreciated
Is it possible / advisable to have a member of a class that is not persisted to the database for a rails model?
I want to store the last type the user selects in a session variable. Since I cant set the session variable from my model, I want to store the value in a "dummy" class member that just passes the value back to the controller.
Can you have such a class member?
Adding non-persisted attributes to a Rails model is just like any other Ruby class:
class User < ActiveRecord::Base
attr_accessor :someattr
end
me = User.new(name: 'Max', someattr: 'bar')
me.someattr # "bar"
me.someattr = 'foo'
The extended explanation:
In Ruby all instance variables are private and do not need to be defined before assignment.
attr_accessor creates a setter and getter method:
class User < ActiveRecord::Base
def someattr
#someattr
end
def someattr=(value)
#someattr = value
end
end
There is one special thing going on here; Rails takes the hash you pass to User.new and maps the values to attributes. You could simulate this behavior in a plain ruby class with something like:
class Foo
attr_accessor :bar
def initialize(hash)
hash.keys.each do |key|
setter = "#{key}=".intern
self.send(setter, hash[key]) if self.respond_to? setter
end
end
end
> Foo.new(bar: 'baz')
=> <Foo:0x0000010112aa50 #bar="baz">
Classes in Ruby can also be re-opened at any point, ActiveRecord uses this ability to "auto-magically" add getters and setters to your models based on its database columns (ActiveRecord figures out which attributes to add based on the database schema).
Yes you can, the code below allows you to set my_class_variable and inside the model reference it as #my_class_variable
class MyCLass < ActiveRecord::Base
attr_accessor :my_class_variable
def do_something_with_it
#my_class_variable + 10
end
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
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')
I have problem with mongomapper associations. I have one class names User and other named Model. User has many models but...
user = User.first
=> <User ...
user.models
=> []
Model.find_by_user_id(user.id.to_s)
=> <Model ...
Model.find_by_user_id(user.id.to_s).user == user
=> true
Class code (simplified):
class User
include MongoMapper::Document
# some keys definition
many :models
end
class Model
include MongoMapper::Document
# some keys definitions
belongs_to :user
end
What I am doing wrong?
It appears that MM no longer uses String format for the FK column, so
Model.find_by_user_id(user.id.to_s)
should be
Model.find_by_user_id(user.id)
Furthermore, the datatype of the Model.user_id column should be set to
key :user_id, Mongo::ObjectID
When I ran into this problem, I had to delete and recreate my collection to get it to work- in other words I used to have user_id as a String, but it would only "take" when I switched it when I rebuilt my database. Luckily I am working with test data so that was easy enough.
What kind of errors or exceptions are you getting? The code you posted looks fine.
ah, this is poorly documented in the mm docs. You need to do this here:
class User
include MongoMapper::Document
# some keys definition
many :models, :in => :model_ids
end
class Model
include MongoMapper::Document
# some keys definitions
# no belongs_to necessary here
end
You can then add models to your user via:
# use an existing object
u = User.create ...
m = Model.create ...
# and add the model to the user
u.models << m
# don't forget to save
u.save
# you can then check if it worked like so:
# u.model_ids => [ BSON::ID 'your user id']
Hope that helped.