I have a model with two has_many associations that serve one logical purpose, and I want to alternate between the two depending on some conditions. I can have a convenience method for that, but then I can't use it in has_many :through associations.
Is there a nice way out?
UPD: the model's code:
# encoding: UTF-8
class User < ActiveRecord::Base
set_table_name 'clients'
devise(:database_authenticatable,
#:registerable,
:recoverable,
:rememberable,
:trackable,
:validatable,
#:token_authenticatable,
#:confirmable,
#:lockable
#:timeoutable,
#:omniauthable
)
def email_required?
false
end
# Setup accessible (or protected) attributes for your model
attr_accessible(:email, :login, :password, :password_confirmation, :remember_me, # used by devise
:address, :phone, :description)
attr_accessible :name, :address, :phone, :description
validates :name, :presence => true
has_many :slaves, :class_name => 'User', :foreign_key => 'master_id',
:inverse_of => :master, :dependent => :destroy
belongs_to :master, :class_name => 'User', :foreign_key => 'master_id',
:inverse_of => :slaves
def slave?
master.present?
end
def master?
not slave?
end
validate :slaves_cannot_have_slaves
has_many :master_facilities, :class_name => 'Facility', :foreign_key => 'client_id'
has_many :analytics_profiles, :class_name => 'AnalyticsProfile', :foreign_key => 'owner_id',
:inverse_of => :owner, :dependent => :destroy
has_many :facility_permissions
has_many :slave_facilities, through: :facility_permissions, source: :facility, autosave: true
has_many :units, :through => :facilities, :foreign_key => 'facility_id'
# masters and slaves have different ways of accessing their facilities
# BUT! It's not a true association so a lot of code (e.g. draper) will fail
def facilities
if master?
master_facilities
else
slave_facilities
end
end
def dead_units
self.units.keep_if(&:dead?)
end
private
def slaves_cannot_have_slaves
unless master? or slaves.empty?
errors.add :slaves, 'Slaves cannot have slaves'
end
end
end
If I understood the problem right and the problem is the facilities association, which is implemented via master_facilities or slave_facilities association - why not taking the base behavior out to a module/class and include/inherit it in your concrete classes which will implement the facilities method differently.
Related
I just upgraded to Rails 3.2.10 and am getting an error message that I never used to get when updating a record via RailsAdmin.
ActiveRecord::HasManyThroughNestedAssociationsAreReadonly at /admin/vendor/12/edit
Message Cannot modify association 'Vendor#categories' because it goes through more than one other association.
This is my Vendor model:
class Vendor < ActiveRecord::Base
attr_accessible :name, :description, :banner_image, :logo_image, :intro_text, :thumb_image, :category_ids, :product_ids, :user_id, :remove_banner_image, :banner_image_cache, :remove_logo_image, :logo_image_cache
mount_uploader :banner_image, ImageUploader
mount_uploader :logo_image, ImageUploader
mount_uploader :thumb_image, ImageUploader
has_many :products, :dependent => :destroy
has_many :categories, :through => :products
belongs_to :owner, :class_name => "User",
:foreign_key => "user_id"
end
This is my Category model:
class Category < ActiveRecord::Base
attr_accessible :name, :product_ids, :category_ids
has_many :category_products do
def with_products
includes(:product)
end
end
has_many :products, :through => :category_products
end
This is my Product model:
class Product < ActiveRecord::Base
attr_accessible :name, :description, :price, :vendor_id, :image, :category_ids, :sku, :remove_image, :image_cache
mount_uploader :image, ImageUploader
belongs_to :vendor
has_many :category_products do
def with_categories
includes(:category)
end
end
has_many :categories, :through => :category_products
end
This is my CategoryProduct model:
class CategoryProduct < ActiveRecord::Base
attr_accessible :product_id, :category_id, :purchases_count
belongs_to :product
belongs_to :category
validates_uniqueness_of :product_id, :scope => :category_id
end
This happens because your association is nested, meaning (from rails source) :
A through association is nested if there would be more than one join table... which is your case here.
Apparently a workaround (I didn't test) is telling Vendor it doesn’t need to autosave the association.
has_many :categories, :through => :products, :autosave => false
You can mark the association as readonly and rails_admin will then not generate the category fields in the form for vendor:
has_many :categories, -> { readonly }, through: :products
In my project I need User to be able to both "create/own" its own objects and "track" (or "follow") objects created by other users. Here is my approach:
class User < ActiveRecord::Base
has_many :widgets
has_many :gadgets
has_many :nuggets
has_many :tracks
has_many :tracked_widgets, :as => :trackable, :trackable_type => "Widget"
has_many :tracked_gadgets, :as => :trackable, :trackable_type => "Gadget"
has_many :tracked_nuggets, :as => :trackable, :trackable_type => "Nugget"
end
class Track < ActiveRecord::Base
belongs_to :trackable, polymorphic: true
belongs_to :user
end
class Widget < ActiveRecord::Base # Gadget and Nugget classes are the same
has_many :tracks, :as => :trackable
end
The schema for the tracks table:
create_table "tracks", :force => true do |t|
t.integer "user_id"
t.integer "trackable_id"
t.string "trackable_type"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "tracks", ["trackable_id", "user_id"], :name => "index_tracks_on_trackable_id_and_user_id"
With this implementation I get the following error:
Unknown key: trackable_type (ArgumentError)
Looking at the schema, it's obvious that I don't have :trackable_type set up as an index, so the error message makes sense. However, in the examples I've seen that demonstrate polymorphic relationships, I've never seen the ______able_type field set up as an index. This makes me wonder if I'm missing something basic here. I have these questions:
Have I somehow incorrectly set up the has_many and :as => :trackable relationships?
If not, is there any issue using a string field as an index?
If the answer is simply to add the :trackable_type field as an index, what is the best way to do this after the fact? Add a new migration that creates the :trackable_type index?
I finally figured out how to make the polymorphic association work for this case. Not sure I understand why, but the :as option for has_many is not used in some polymorphic cases. Instead you must use a standard :through option and then reestablish the polymorphism with the :source and :source_type options.
In my example above, the has_many statements in the User model the needed to be formatted as follows:
has_many :tracked_widgets, :through => :tracks, :source => :trackable, :source_type => "Widget"
has_many :tracked_gadgets, :through => :tracks, :source => :trackable, :source_type => "Gadget"
has_many :tracked_nuggets, :through => :tracks, :source => :trackable, :source_type => "Nugget"
Hopes this helps anyone trying to address a similar situation.
I'm working on a ActiveAdmin app with this models :
User
class User < ActiveRecord::Base
# A User has many roles for interact on a project
has_many :roles, :dependent => :destroy
has_many :projects, :through => :role
end
Role
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
Project
class Project < ActiveRecord::Base
# A project has many roles for interact
has_many :roles, :dependent => :destroy
has_many :users, :through => :role
accepts_nested_attributes_for :roles
end
To add users with a role on each project I make this form :
form do |f|
f.inputs "Details" do # Project's fields
f.input :title
f.input :code
end
f.has_many :roles do |app_f|
app_f.inputs do
if !app_f.object.nil?
app_f.input :_destroy, :as => :boolean, :label => "Destroy?"
end
app_f.input :user
app_f.input :senior_author
end
end
f.buttons
end
My first question is how can I make a with user.firstname + user.lastname. Actually I have something like this :
#<User:0x007fb98a7d6568>
Second question is my Role model is a list of boolean attributes :
:senior_author
:first_author
:viewer
....
Can I make a with that ?
Another solution would be to just define to_s in the model:
def to_s
"#{email} | #{firstname} #{lastname}"
end
No need to set :label_method.
Just add :label_method => lambda:
app_f.input :user, :label_method => lambda{|u| "#{u.email} | #{u.firstname} #{u.lastname}" }
I fix it by adding this method to models/user.rb
# format label for formtastic dropdown menu
def to_label
"#{email} | #{firstname} #{lastname}"
end
And I use it like this :
app_f.input :user, :include_blank => false, :label_method => :to_label
Good morning fellow Overflowers,
Small problem with model associations. I have these model associations:
class Categorization < ActiveRecord::Base
belongs_to :exhibit
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :categorizations
has_many :exhibits, :through => :categorizations
acts_as_indexed :fields => [:title]
validates :title, :presence => true, :uniqueness => true
end
class Exhibit < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations, :source => :category
acts_as_indexed :fields => [:title, :bulb]
validates :title, :presence => true, :uniqueness => true
belongs_to :foto, :class_name => 'Image'
end
So, essentially Categorization ends up with these columns (date/time stamps omitted):
categorization_id, exhibit_id and category_id.
My problem is that when I delete an Exhibit, its reference on the Categorization table is not deleted thus getting a DB error on my view. I have to first unassign the Exhibit from any Category and then delete it safely. Or (given for example that the Exhibit I delete has :exhibit_id=>'1') when I give in the rails console: Categorization.find_by_exhibit_id(1).destroy
Thanks for any help!!
You can set the :dependent options on associations that you want Rails to follow when you delete their parents:
class Exhibit < ActiveRecord::Base
has_many :categorizations, :dependent => :destroy
...
end
So, got 2 models:
class Match < ActiveRecord::Base
has_many :rounds
has_many :participations
has_many :players, :through => :participations
belongs_to :clan_1, :class_name => "Clan", :foreign_key => "clan_1_id"
belongs_to :clan_2, :class_name => "Clan", :foreign_key => "clan_2_id"
belongs_to :winner, :class_name => "Clan", :foreign_key => "winner_id"
belongs_to :league
belongs_to :tournament
validates :clan_1_id, :presence => true
validates :clan_2_id, :presence => true
scope :by_league, lambda { |league| where("league_id == ?",league.id) }
scope :by_tournament, lambda { |tournament| where("tournament_id == ?",tournament.id) }
scope :played, where("played is not NULL")
scope :not_played, where("played is NULL")
end
class Clan < ActiveRecord::Base
has_many :players
has_many :rounds_won, :class_name => "Round", :foreign_key => "winner_id"
has_many :rounds_blue, :class_name => "Round", :foreign_key => "clan_blue_id"
has_many :rounds_purple, :class_name => "Round", :foreign_key => "clan_purple_id"
has_many :matches_won, :class_name => "Match", :foreign_key => "winner_id"
has_and_belongs_to_many :leagues
has_and_belongs_to_many :tournaments
def matches
Match.where("clan_1_id = ? OR clan_2_id = ?",self.id, self.id)
end
def matches_lost
matches.where("winner_id != ?", self.id)
end
def matches_drawn
matches.played.where("winner_id is NULL")
end
end
and I want to fetch all clans, which taken part in match.
You're over thinking it. Rails makes it very easy for you to do this.
class Comment < ActiveRecord::Base
belongs_to :post
end
class Post < ActiveRecord::Base
has_many :comments
end
#post.comments
If you have a column in your comment table with "modelName"_id (eg post_id) rails with automatically hook up the foreign key.
All you have to do is call #model1.model2 assuming #model1 is an instance of the model1 object.
If you want to hook up the query yourself you could use the where() method.
#comments = Comment.where(:post_id => some_id)
If you alter your associations a little bit you can utilize includes() in scopes:
class Match < ActiveRecord::Base
belongs_to :clan_1, :class_name => "Clan"
belongs_to :clan_2, :class_name => "Clan"
end
class Clan < ActiveRecord::Base
has_many :one_matches, :class_name => 'Match', :foreign_key => :clan_1_id
has_many :two_matches, :class_name => 'Match', :foreign_key => :clan_2_id
end
Then you can add this scope:
class Clan < ActiveRecord::Base
scope :participated_in_match, includes(:one_matches, :two_matches).where("matches.id IS NOT NULL")
end
This isn't tested so please let me know if you get unexpected results.
Quite simply:
model_two_object = Model_2.first # For clarity only, change to suit your needs
model_two_object.models_1