Why is the associated model being touched? - ruby-on-rails

I have 3 models:
class DeliveryMethod
has_many :subscription_delivery_methods
has_many :subscriptions, :through => :subscription_delivery_methods
end
class SubscriptionDeliveryMethod < ActiveRecord::Base
belongs_to :subscription
belongs_to :delivery_method
end
class Subscription < ActiveRecord::Base
has_one :subscription_delivery_method
has_one :delivery_method, :through => :subscription_delivery_method
end
I assign delivery method to Subscription like this:
s.delivery_method = DeliveryMethod.find 1
When I do the assignment like above Rails updates subscription_delivery_methods, which is expected:
UPDATE subscription_delivery_methods SET delivery_method_id = 1, updated_at = '2011-10-27 09:11:23' WHERE subscription_delivery_methods.id = 2
But when I do s.save! it touches DeliveryMethod, which is unexpected and unwanted:
UPDATE delivery_methods SET updated_at = '2011-10-27 08:40:53' WHERE delivery_methods.id = 1
I tried all kinds of :readonly and :touch flags on the associations to prevent this update from happening. I haven't succeeded. Do you guys know how to stop it?
Thanks a lot.

Maybe autosave?
has_one :delivery_method, :through => :subscription_delivery_method, :autosave => false

Related

Complex ActiveRecord Query

I'm having a hard time wrapping my head around how to build this query. I'm not sure if I should try to create a bunch of scopes and try to chain them. Or do I put it into a class method? Or would I do a combo of both? If anyone could give me a short example it would keep me from jumping out the window, I've been working on this for over a week now.
class CensusDetail < ActiveRecord::Base
belongs_to :apartment
belongs_to :resident
end
class Apartment < ActiveRecord::Base
belongs_to :apartment_type
has_many :census_details
has_many :residents, :through => :census_details
end
class ApartmentType < ActiveRecord::Base
has_many :apartments
end
class Resident < ActiveRecord::Base
has_many :census_details
has_many :apartments, :through => :census_details
end
apartment.rent_ready = boolean value if apartment is ready
apartment_type.occupany = the number of allowed occupants in an apartment
census_detail.status = either "past", "present", or "new"
census_detail.moveout_date = date time resident is scheduled to move out
I need to build a query that does the following:
- if the apartment is rent ready then do the following:
- pull a list from census_details of all residents set as "current" for each apartment
-if the # of residents is less than the value of apartment_type.occupancy for this
apartment, then list it as available
-if the # of residents is = to the value of apartment_type.occupancy then
-does any resident have a scheduled move out date
-if not, do not list the apartment
-if yes, then list apartment
Thanks in advance for any help or suggestions.
I haven't cleaned it up yet, but this is working for me, so I wanted to share.
apartment_type.rb
class ApartmentType < ActiveRecord::Base
has_many :apartments, :dependent => :nullify
end
census_detail.rb
class CensusDetail < ActiveRecord::Base
belongs_to :resident_contacts
belongs_to :apartments
scope :get_current_residents, ->(id) { where("status = ? and apartment_id = ?", "current", id) }
end
apartment.rb where most of the work is happening
class Apartment < ActiveRecord::Base
belongs_to :apartment_type
has_many :census_details
has_many :residents, :through => :census_details
scope :rent_ready, -> { where(:enabled => true) }
def self.available
available_apartments = []
rent_ready_apartments = self.rent_ready.all
rent_ready_apartments.each do |apt|
tmp = ApartmentType.where('id = ?', apt.apartment_type_id).pluck(:occupancy)
occupancy = tmp[0]
current_residents = CensusDetail.get_current_residents(apt.id)
resident_count = current_residents.count
if resident_count < occupancy
available_apartments << apt
end
if resident_count = occupancy
scheduled = false
current_residents.each do |res|
if res.moveout_date
scheduled = true
end
end
if scheduled == true
available_apartments << apt
end
end
end
available_apartments
end
end
The code is a bit ugly but its so far passing my tests. I would have edited my original question instead of answering in case someone has a better way of doing this but each time I do my question gets voted down. If anyone knows a better way please let me know because I'm at a point in the app where there are about 50 tables and now it's all getting tied together with complex queries.

Accessing singular_association_ids from model in Rails

I've been using the association_collection method "other_ids" throughout my Rails app with no issues. However whenever I try to access it from within the model defining the association, Rails has no idea what I'm taking about. For example:
class Membership < ActiveRecord::Base
belongs_to :course, :touch => true
belongs_to :person, :touch => true
end
class Day < ActiveRecord::Base
belongs_to :course, :touch => true, :counter_cache => true
has_many :presents, :dependent => :delete_all
has_many :people, :through => :presents
before_destroy :clear_attendance
def clear_attendance
mems = Membership.where(:course_id => course.id, :person_id => person_ids)
mems.update_all(["attendance = attendance - ?", (1 / course.days.size.to_f)])
end
end
In this case, person_ids is always null. I've tried self.person_ids, people.ids, etc. All nothing. I have used day.person_ids elsewhere with no issues, so why can't I use it here?
I am using Ruby 1.9.1 and Rails 3.0.3. Here is the SQL call from my log:
[1m[36mAREL (0.0ms)[0m [1mUPDATE "memberships" SET attendance = attendance - 0.3333333333333333 WHERE ("memberships"."course_id" = 4) AND ("memberships"."person_id" IN (NULL))[0m
edit: added more code to clarify question
What you really want there is:
def a_method
self.people.all
end
But to answer your question, person_ids is the correct method, and it should return an empty array, not nil. I just tried an association like that out in 2.3.10. Maybe you can post some more of your code, rails version, etc.
Thanks for your help - I figured it out myself. The problem was the order of my callbacks. I was trying to call person_ids after the association had been deleted. Changing the order to this solved my issues.
class Day < ActiveRecord::Base
before_destroy :clear_attendance
belongs_to :course, :touch => true, :counter_cache => true
has_many :presents, :dependent => :delete_all
has_many :people, :through => :presents

(Rails 3) Combine two queries into one

I have these models simplified:
class Game::Champ < ActiveRecord::Base
has_one :contract, :class_name => "Game::ChampTeamContract", :dependent => :destroy
has_one :team, :through => :contract
# Attributes: :avg => integer
end
#
class Game::Team < ActiveRecord::Base
has_many :contracts, :class_name => "Game::ChampTeamContract", :dependent => :destroy
has_many :champs, :through => :contracts
end
#
class Game::ChampTeamContract < ActiveRecord::Base
belongs_to :champ
belongs_to :team
# Attributes: :expired => bool, :under_negotiation => bool
end
#
So what I want to do here is to find all Game::Champs that have no Game::ChampTeamContract whatsoever OR has, but (is not :under_negociation OR is :expired ), sorted by Champ.avg ASC
I am kinda stuck at using two queries, concating the result and sorting it. I wish there were a better way to to it more "Railish"
UPDATE: Just added a constraint about :expired
Try something like:
Game::Champs.
joins("left outer join game_champ_team_contracts on game_champ_team_contracts.champ_id = game_champs.id").
where("game_champ_team_contracts.id is null or (game_champ_team_contracts.state != ? or game_champ_team_contracts.state = ?)", :under_negotiation, :expired).
order("game_champs.avg ASC")
This is a fairly nasty line if left as-is, so if you use this, it needs tidying up. Use scopes or methods to split it up as much as possible!
I just tested with a super simple query:
#bars1 = Bar.where(:something => 1)
#bars2 = Bar.where(:something => 2)
#bars = #bars1 + #bars2
Not sure if it's right, but it works...

Belongs_to based on value of a field

I have a table with entries, and each entries can have different account-types. I'm trying to define and return the account based on the value of cindof
Each account type has one table, account_site and account_page. So a regular belongs_to won't do.
So is there any way to return something like:
belongs_to :account, :class_name => "AccountSite", :foreign_key => "account_id" if cindof = 1
belongs_to :account, :class_name => "AccountPage", :foreign_key => "account_id" if cindof = 2
Have tried to do that in a method allso, but no luck. Really want to have just one accountand not different belongs_to names.
Anyone that can figure out what I want? Hard to explain in English.
Terw
You should be able to do what you want with a polymorphic association. This won't switch on cindof by default, but that may not be a problem.
class ObjectWithAccount < ActiveRecord::Base
belongs_to :account, :polymorphic => true
end
class AccountSite < ActiveRecord::Base
has_many :objects_with_accounts,
:as => :account,
:class_name => 'ObjectWithAccount'
end
class AccountPage < ActiveRecord::Base
has_many :objects_with_accounts,
:as => :account,
:class_name => 'ObjectWithAccount'
end
You will need both an account_id column and a account_type column. The type of the account object is then stored in the extra type column.
This will let you do:
obj.account = AccountPage.new
or
obj.account = AccountSite.new
I would look into Single Table Inheritance. Not 100% sure, but I think it would solve your problem http://code.alexreisner.com/articles/single-table-inheritance-in-rails.html
If that isn't good, this isn't too hard to implement yourself.
def account
case self.cindof
when 1 then AccountSite.find self.account_id
when 2 then AccountPage.find self.account_id
end
end

Rails model relations depending on count of nested relations

I am putting together a messaging system for a rails app I am working on.
I am building it in a similar fashion to facebook's system, so messages are grouped into threads, etc.
My related models are:
MsgThread - main container of a thread
Message - each message/reply in thread
Recipience - ties to user to define which users should subscribe to this thread
Read - determines whether or not a user has read a specific message
My relationships look like
class User < ActiveRecord::Base
#stuff...
has_many :msg_threads, :foreign_key => 'originator_id' #threads the user has started
has_many :recipiences
has_many :subscribed_threads, :through => :recipiences, :source => :msg_thread #threads the user is subscribed to
end
class MsgThread < ActiveRecord::Base
has_many :messages
has_many :recipiences
belongs_to :originator, :class_name => "User", :foreign_key => "originator_id"
end
class Recipience < ActiveRecord::Base
belongs_to :user
belongs_to :msg_thread
end
class Message < ActiveRecord::Base
belongs_to :msg_thread
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
end
class Read < ActiveRecord::Base
belongs_to :user
belongs_to :message
end
I'd like to create a new selector in the user sort of like:
has_many :updated_threads, :through => :recipiencies, :source => :msg_thread, :conditions => {THREAD CONTAINS MESSAGES WHICH ARE UNREAD (have no 'read' models tying a user to a message)}
I was thinking of either writing a long condition with multiple joins, or possibly writing giving the model an updated_threads method to return this, but I'd like to see if there is an easier way first. Am I able to pass some kind of nested hash into the conditions instead of a string?
Any ideas? Also, if there is something fundamentally wrong with my structure for this functionality let me know! Thanks!!
UPDATE:
While I would still appreciate input on better possibilities if they exist, this is what I have gotten working now:
class User < ActiveRecord::Base
# stuff...
def updated_threads
MsgThread.find_by_sql("
SELECT msg_threads.* FROM msg_threads
INNER JOIN messages ON messages.msg_thread_id = msg_threads.id
INNER JOIN recipiences ON recipiences.msg_thread_id = msg_threads.id
WHERE (SELECT COUNT(*) FROM `reads` WHERE reads.message_id = messages.id AND reads.user_id = #{self.id}) = 0
AND (SELECT COUNT(*) FROM recipiences WHERE recipiences.user_id = #{self.id} AND recipiences.msg_thread_id = msg_threads.id) > 0
")
end
end
Seems to be working fine!
Also to check if a specific thread (and message) are read:
class Message < ActiveRecord::Base
# stuff...
def read?(user_id)
Read.exists?(:user_id => user_id, :message_id => self.id)
end
end
class MsgThread < ActiveRecord::Base
# stuff...
def updated?(user_id)
updated = false
self.messages.each { |m| updated = true if !m.read?(user_id) }
updated
end
end
Any suggestions to improve this?
Add a named_scope to the MsgThread model:
class MsgThread < ActiveRecord::Base
named_scope :unread_threads, lambda { |user|
{
:include => [{:messages=>[:reads]}, recipiencies],
:conditions => ["recipiences.user_id = ? AND reads.message_id IS NULL",
user.id],
:group => "msg_threads.id"
}}
end
Note: Rails uses LEFT OUTER JOIN for :include. Hence the IS NULL check works.
Now you can do the following:
MsgThread.unread_threads(current_user)
Second part can be written as:
class Message
has_many :reads
def read?(usr)
reads.exists?(:user_id => usr.id)
end
end
class MsgThread < ActiveRecord::Base
def updated?(usr)
messages.first(:joins => :reads,
:conditions => ["reads.user_id = ? ", usr.id]
) != nil
end
end
You might want to take a look at Arel, which can help with complex SQL queries. I believe (don't quote me) this is already baked into Rails3.

Resources