Rails ActiveRecord define database objects within other object - ruby-on-rails-3

I'm relatively new to rails and very new to database manipulation.
I'm trying to create a class within the database which contains a number of custom objects within it. These custom objects are also to be stored in the database in a separate table. I've managed to set this up as follows
class MyClass < ActiveRecord::Base
has_many :other_objects, :dependent => destroy
end
class OtherObject < ActiveRecord::Base
belongs_to :my_class
attr_accessible :some_stuff...
end
I've created the appropriate database tables and managed to get it working.
Now what I want to do is have (four) particular instances of "OtherObject"s in my class, which can be accessed by some straightforward identifier, something like
test = MyClass.new
...
test.instance_of_other_object.some_attribute = "blahblah"
Such that this updates the database entry for the associated object. What is the best way to go about this?

That has_many association sets up MyClass#other_objects (and a bunch of other methods) to allow you to easily work with associated records.
You probably want:
my_class.other_objects.each do |other_object|
other_object.update_attributes(:foo => 'bar')
end
If you want a direct SQL update, you can use update_all:
my_class.other_objects.update_all(:foo => 'bar')
Update:
If that's the sort of association you need, you may define a belongs_to association:
class MyClass < ActiveRecord::Base
has_many :other_objects, :dependent => :destroy
# uses :selected_other_object_id
belongs_to :selected_other_object, :class_name => "OtherObject"
end
my_class = MyClass.first
my_class.selected_other_object = other_object # Set the object.
# => #<OtherClass:...>
my_class.selected_other_object_id # ID has been set.
# => 10
my_class.selected_other_object # Retrieve the object.
# => #<OtherClass:...>
my_class.selected_other_object.save # Persist ID and other fields in the DB.
my_class = MyClass.find(my_class.id) # If you fetch the object again...
# => #<MyClass:...>
my_class.selected_other_object_id # The ID is still there.
# => 10
my_class.selected_other_object # You have the ID, you get the object.
# => #<OtherClass:...>
my_class.selected_other_object.foo = "bar" # Access associated object this way.
another_variable = my_class.selected_other_object # Or this way.
Remember however that this does not assume that :selected_other_object is a subset of :other_objects.
Also note that the selected_other_object and selected_other_object= methods are already set up when you set up the association, so you don't have to define these yourself.

This is not a complete answer but I have come up with something that works for getting the object, but not for setting it.
class MyClass < ActiveRecord::Base
has_many :other_objects, :dependent => destroy
def particular_instance
return OtherObject.find_by_id(self.particular_instance_id)
end
end
Where my db schema looks like this
create_table "my_classs", :force => true do |t|
t.integer "particular_instance_id"
end
create_table "other_objects", :force => true do |t|
t.integer "my_class_id"
t.string "some_attribute"
end
Update
In order to set an attribute of the other_object class the update_attributes method can be used
my_class.particular_instance.update_attributes(:some_attribute => "blah")

Use object.dup now for rails 3.1 and on.
a = MyClass.first # finds the first instance of the model "MyClass"
b = a.dup # duplicates the record.
c = a.dup # duplicates the record again.
b.field_name = "blahblah"
c.fielf_name = "blahblah"
b.save # saves the record into the database for object b.
c.save # saves the record into the database for object c.
If you look into your database, you can see that a new record has been created. It is identical to the first record excepted that it has a new id.
Also check Duplicate a model for more information about it.

Related

How to rewrite Rails associations without separate queries (looping through them again)

Running into some performance issues with the following code (stripped out irrelevant parts).
This is the CardsController#index code:
def index
cards = cards.paginate(page: index_params[:page], per_page: limit)
# Assign bumped attribute
cards.each do |card|
if current_user
card.bumped = card.bump_by?(current_user)
card.bump = card.get_bump(current_user)
else
card.bumped = false
card.bump = nil
end
end
end
Card.rb:
class Card < ActiveRecord::Base
belongs_to :cardable, polymorphic: true, touch: true
belongs_to :user
has_many :card_comments, autosave: true
has_many :card_bumps
has_many :card_bumpers, through: :card_bumps, class_name: 'User', source: :user
def bump_by?(user)
self.card_bumpers.include? user
end
def get_bump(user)
CardBump.find_by(user_id: user.id, card_id: self.id)
end
end
How can I avoid and optimize the second loop on each card where I do the associations of card.bumped and card.bump ?
Thanks in advance
In the model level, since the method bump_by? equals to bump existence, so
card.bumped = !card.bump.empty?
So the whole includes check in method bump_by? can be avoided, which in turn avoid fetching all associated bumps.
First of all, I would optimize your controller code a little and would update all cards with a single query if current_user is not present:
def index
cards = cards.paginate(page: index_params[:page], per_page: limit)
# Assign bumped attribute
if current_user
cards.each do |card|
card.bump = card.get_bump(current_user)
card.bumped = card.bump_by?(current_user)
end
else
cards.update_all(bump: nil, bumped: false)
end
end
There is also a possibility to optimize model code:
class Card < ActiveRecord::Base
belongs_to :cardable, polymorphic: true, touch: true
belongs_to :user
has_many :card_comments, autosave: true
has_many :card_bumps
has_many :card_bumpers, through: :card_bumps, class_name: 'User', source: :user
def bump_by?(user)
# use #exists? to check whether a record is present in the database.
# This will make a `SELECT 1 as count...` query and,
# therefore, perform a lookup on database level.
# #include? in opposite will load ALL associated items from DB,
# turn them into a ruby objects array and perform a lookup in the
# obtained array which is much slower than simple lookup performed by #exists?
get_bump(user) == user || self.card_bumpers.exists?(user.id)
end
def get_bump(user)
#_bump ||= self.card_bumpers.find_by(user_id: user.id)
end
end
Since Card#get_bump is also looking in card_bumpers association we can memorize its result and later use memorized value in Card#bump_by? without hitting database again. If there is no memorized value then fast check for record existence will be performed by a database.
Notice, that I changed lines order in controller to get benefit of memorizing:
card.bump = card.get_bump(current_user)
card.bumped = card.bump_by?(current_user)

Why are individual SELECT queries running when an all-encompassing SELECT already ran? (Rails/ActiveRecord)

I have the following code (note the includes and the .each):
subscribers = []
mailgroup.mailgroup_members.opted_to_receive_email.includes(:roster_contact, :roster_info).each { |m|
subscribers << { :EmailAddress => m.roster_contact.member_email,
:Name => m.roster_contact.member_name,
:CustomFields => [ { :Key => 'gender',
:Value => m.roster_info.gender.present? ? m.roster_info.gender : 'X'
} ]
} if m.roster_contact.member_email.present?
}
subscribers
Correspondingly, I see the following in my logs (i.e. select * from ROSTER_INFO ... IN (...)):
SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` IN ('1450', '1000', '1111')
Yet immediately after that there are select * from ROSTER_INFO for each ID already specified in the IN list of the previous query:
RosterInfo Load (84.8ms) SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` = '1450' LIMIT 1
RosterInfo Load (59.2ms) SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` = '1000' LIMIT 1
RosterInfo Load (56.8ms) SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` = '1111' LIMIT 1
If select * had already been done on ROSTER_INFO on all IDs of interest (IN (...)), why is another select * being done again for each of the same IDs? Doesn't ActiveRecord already know all the ROSTER_INFO columns for each ID?
(Meanwhile, there are no individual queries for ROSTER_CONTACT, yet if I remove :roster_contact from the includes method, then ROSTER_INFO is not queried again, but ROSTER_CONTACT is.)
RosterInfo model (abridged)
class RosterInfo < ActiveRecord::Base
self.primary_key = 'ID'
end
RosterContact model (abridged)
class RosterContact < ActiveRecord::Base
self.primary_key = 'ID'
has_many :mailgroup_members, foreign_key: 'rosterID'
has_many :mailgroups, through: :mailgroup_members
has_one :roster_info, foreign_key: 'ID' # can use this line
#belongs_to :roster_info, foreign_key: 'ID' # or this with no difference
def member_name # I added this method to this
roster_info.member_name # question only *after* having
end # figured out the problem.
end
RosterWeb model (abridged)
class RosterWeb < ActiveRecord::Base
self.primary_key = 'ID'
end
Mailgroup model (abridged)
class Mailgroup < ActiveRecord::Base
self.primary_key = 'ID'
has_many :mailgroup_members, foreign_key: 'mailCatID'
has_one :mailing_list, foreign_key: :legacy_id
end
MailgroupMember model (abridged)
class MailgroupMember < ActiveRecord::Base
self.primary_key = 'ID'
belongs_to :mailgroup, foreign_key: 'mailCatID'
belongs_to :roster_contact, foreign_key: 'rosterID'
belongs_to :roster_info, foreign_key: 'rosterID'
belongs_to :roster_web, foreign_key: 'rosterID'
scope :opted_to_receive_email, joins(:roster_web).where('ROSTER_WEB.receiveEmail=?', 1)
end
The issue turned out to be related to m.roster_contact.member_name -- unfortunately I made member_name a method of roster_contact that itself (indirectly) queried roster_info.member_name. I resolved this by changing the line
:Name => m.roster_contact.member_name,
to directly query roster_info as follows
:Name => m.roster_info.member_name,
I am sorry for the trouble!
I'm going to stick my neck out and say that this is probably an in-flight optimization by your query engine. The 'IN' is typically used to compare large sets of keys, the most efficient way of resolving three keys (assuming ID is the key) would be to retrieve each row by key, as has happened.
class RosterInfo < ActiveRecord::Base
has_one :roster_contact, foreign_key: 'ID'
end
class RosterContact < ActiveRecord::Base
has_one :roster_info, foreign_key: 'ID'
end
I don't know what is the premise for having bi-directional has_one, but I suspect it will turn out badly. Probably change one of them to belongs_to. Do the same for the other bi-directional has_one associations.
Another thing is that you are using 'ID' for the foreign_key column, where the usual practice is roster_contact_id or whichever class you are referencing.
Edit:
On closer examination, RosterInfo, RosterContact, RosterWeb look like separate tables for what should be a single record since they are all having the same set of mutual has_one associations. This is something that should be addressed on the schema level, but right now you should be able to drop the has_one associations from one of the three models to solve your immediate problem.

How do I insert a record in Rails when there's no model and without using SQL

I have created a table that implements an n-to-n relation using the following statement:
create_table :capabilities_roles, :id => false do |t|
t.integer :capability_id, :null => false
t.integer :role_id, :null => false
end
There is no model for this table. How do I insert records without resorting to SQL?
I found this in the ActiveRecord::ConnectionAdapters::DatabaseStatements module:
insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
and also:
insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
I have no idea what arel means. Can someone give me an example of a valid insert? I would like to use stuff like :role_id => Role.find_by_name('Business user') in it.
If you're going to be manipulating the database records via Rails, then there should be a model for it. Just create a role.rb in your models directory with the lines
class Role < ActiveRecord::Base
end
And you're as good as gold.
It looks like a join table for has and belongs to many relationship between Capability and Role models. You should let the Rails handle it for you. First define required associations:
class Capability < ActiveRecord::Base
has_and_belongs_to_many :roles
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :capabilities
end
Then just add instance of Role model to roles array of an instance of Capability model (or vice versa):
capability.roles << role
role.capabilities << capability
Removing records from join table is done via removing object from an array:
capability.roles -= [role]
In our project we have many meta tables which don't have models. To generate active record models on the fly we use follow module:
module EntityModel
module_function
ACCESS_MODELS = {}
def for(table_name)
return ACCESS_MODELS[table_name] if ACCESS_MODELS.has_key?(table_name.id)
ACCESS_MODELS[table_name] = create_access_model(table_name)
end
def create_access_model(table_name)
Class.new(ActiveRecord::Base) do
self.table_name = table_name
end
end
end
This create anonymous models and store it in the global hash for performance purposes.
Uses as:
EntityModel.for(:users)

Sunspot / Solr / Rails: Model Associations are not updating in the Index

I have a Fieldnote model in my app, which has_many :activities attached to it through a table called :fieldnote_activities. I then define a searchable index this way:
searchable :auto_index => true, :auto_remove => true do
integer :id
integer :user_id, :references => User
integer :activity_ids, :multiple => true do
activities.map(&:id)
end
text :observations
end
And then I have a Search model to store / update searches. The search model thus also has its own associations with activities. I then perform my searches like this:
#search = Search.find(params[:id])
#query = Fieldnote.search do |query|
query.keywords #search.terms
if #search.activities.map(&:id).empty? == false
query.with :activity_ids, #search.activities.map(&:id)
end
end
#fieldnotes = #query.results
Now this all works GREAT. The problem is that if I change which activities that are associated with a fieldnote, the search results do not change because it appears the indices for that fieldnote do not change. I was under the impression that the :auto_index => true and :auto_remove => true flags when I define the searchable index would keep track of new associations (or deleted associations), but this appears not to be the case. How do I fix this?
You're right that :auto_index and :auto_remove don't apply to associated objects, just the searchable object they are specified on.
When denormalizing, you should use after_save hooks on the associated objects to trigger a reindex where necessary. In this case, you want changes to the Activity model and the FieldnoteActivity join model to trigger a reindex of their associated Fieldnote objects when saved or destroyed.
class Fieldnote
has_many :fieldnote_activities
has_many :activities, :through => :fieldnote_activities
searchable do
# index denormalized data from activities
end
end
class FieldnoteActivity
has_many :fieldnotes
has_many :activities
after_save :reindex_fieldnotes
before_destroy :reindex_fieldnotes
def reindex_fieldnotes
Sunspot.index(fieldnotes)
end
end
class Activity
has_many :fieldnote_activities
has_many :fieldnotes, :through => :fieldnote_activities
after_save :reindex_fieldnotes
before_destroy :reindex_fieldnotes
def reindex_fieldnotes
Sunspot.index(fieldnotes)
end
end

Using ActiveRecord::Association methods where associated "child" model uses STI

I've got a super-class (model) Measurement and two sub-classes: WeightMeasurement and LengthMeasurement.
I've then got a Person class which as many WeightMeasurements and LengthMeasurements.
The issue is when creating a new measurement for a Person, I want to use a shared controller that will handle both weight and length measurements.
However, the way that I would typically build up a Person's measurements would be access them bia the parent (Person). Like person.weight_measurement.build. The problem is that I don't know what to put here... person..build ?
# Base-model, includes "type" column in database.
class Measurement < ActiveRecord::Base
belongs_to :person
end
# model subclass
class WeightMeasurement < Measurement
end
# model subclass
class LengthMeasurement < Measurement
end
class Parent < ActiveRecord::Base
has_many :weight_measurements, :dependent => :destroy
has_many :length_measurements, :dependent => :destroy
end
# Single controller for "Measurements"
class MeasurementsController < ApplicationController
...
def new
person = Person.find(params[:person_id])
#
normally would do this, but because I am using STI,
# I don't know that it is a Person's "weight" measurement we are creating
#
# #measurement = #person.weight_measurements.build
#
...
end
...
end
What I normally do, is to create a hidden field in my form, which contains the type I am trying to create.
<%= hidden_field_tag :type, "weight_measurement" %>
You could also have it as a visible form option (say a radio button or select - instead of the hidden field above)
In your controller, you can do the following then:
if ["length_measurement", "weight_measurement"].include?(params[:type])
#measurement = "Measurement::#{params[:type].classify}".constantize.new(:person => #person)
end