I've been reading and re-reading the Rails associations guide:
http://guides.rubyonrails.org/association_basics.html
This were close but not quite the same:
Ruby on rails active record associations
I'm not sure how to setup the following scenario.
Events has a status of either pending, open, or close.
I thought this would be simple enough to just have:
event has_one status
status belongs_to event
But this really isn't a one-to-one relationship since a status can belong to many events.
So then I thought I would do something like:
status has_many events
event belongs_to status
But this seems funny, because a status doesn't own an event. An event owns a status, right?
I had tried using enumerations and not have a status model. But that got tricky since it seems like ActiveRecord doesn't really support enumerations. I also figured that having a separate model might be good in case someone wants to expand on the number of options for status, like adding 'awaiting approval' or something.
This post suggests that my latter setup is okay, even though it reads funny:
Really easy Rails Active-Record Associations question
But I'm just wondering if I'm not aware of a better Ruby/Rails way of handling this simple scenario.
Thanks in advance!
Doing this as an active record association is overkill. Think about it, create a whole table just to store 3 values in it that will never change?
What you really need is an enum. But of course, ruby doesn't have an enum.
Luckily, you can fake it.
module StatusCodes
pending = 0
open = 1
closed = 2
end
Then you can do something like this
if #event.status == StatusCodes::open
# do something
end
It's a much simpler solution and your code stays very readable.
Ignore that voice in your head: you're doing it fine. The real important of which model has belongs_to is where the foreign key is stored. It's clear in this example that the foreign key should be stored in the Event model, which means it should belongs_to :status.
I also agree with the other posts, though - if you have got a small and fixed number of potential Status records, consider creating an constant hash to store them instead of creating a whole database table for them.
Why not add a status column to Event (as an integer), and have something like this:
class Event < ActiveRecord::Base
STATUS_TYPES = {1 => "active", 2 => "inactive", 3 => "closed"}
def status
STATUS_TYPES[self[:status]]
end
def status=(new_status)
new_status = STATUS_TYPES.invert[new_status] if new_status.class == "String"
self[:status] = new_status
end
end
You might want to consider using state_machine and a simple string column to implement the status, instead of using an association or a hand-rolled enum.
You are wrong.
If you do
Event
has_one :status
Status
belongs_to :event
Rails will make sure it is a one-to-one association, so status will only belong to one event
I'm pretty sure this is what happens if you try to assign a event status to a different event
e1 = Event.first
status = e1.status
e2 = Event.new
e2.status = status
e2.save
Event.first.status #=> nil
Related
I'm wanting to add a function to a model of mine to consolidate two records. If I have PlayerA and PlayerB for instance, and want to destroy PlayerB but assign all his current records to PlayerA. I was going to make a method that would look up all his records and replace with PlayerA's id like so...
#...
Note.where(player_id: PlayerB.id).update_all(player_id: PlayerA.id)
HighScores.where(player_id: PlayerB.id).update_all(player_id: PlayerA.id)
Friends.where(player_id: PlayerB.id).update_all(player_id: PlayerA.id)
#...
Now if I do it this way I would have to remember to add one for each association that gets created in the future for the Player's Model. Is there a way I could have this done dynamically, through perhaps some methods to pull all associations, so I wouldn't have to manually add for each new association?
Seems like a right place for ActiveRecord::Reflection.
Player.reflect_on_all_associations # returns an array of all associations
Player.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
So may be something like this could work for you:
associations = Player.reflect_on_all_associations(:has_many)
associations.each do |association|
associated_class = association.class_name.constantize # e.g. Note
foreign_key = association.foreign_key # e.g. player_id
associated_class.where(foreign_key => PlayerB.id).
update_all(foreign_key => PlayerA.id))
end
Note that this could be more tricky for complex associations like has_and_belongs_to_many though. For the sake of simplicity I do not cover that case here, but you could implement some conditional logics based on association.class which is a one of ActiveRecord::Reflection subclasses (BelongsToReflection, HasAndBelongsToManyReflection, HasManyReflection, HasOneReflection).
Not sure if there is a fast way for this, would love to hear if there is, but I did it the long way. I kept the old data around with an "imported" state so that it can be viewed when desired without getting in the way, and created a new version of each record, for each association type, with the new player's details.
Suppose I have posts, which have many categories through categorizations. Suppose that I add a boolean column primary to categorizations in order to determine the primary category of a post. But now I'm stuck dealing with the join model, when what I'd really like to do is something like this:
post = Post.first
primary_cat = post.categories.where(:primary => true)
post.categories.first.primary = true
post.save # would actually update the categorization, setting primary = true
There are all sorts of examples I could give you to show why this would be useful, but essentially I want to be able to interact with a model as though it is somehow merged with its join model. Being able to say "What's the primary category?" or "OK this category will be the primary one" without ever touching the join model is the intuitive for me the think of this.
Is this even possible with Rails? Has anyone seen a effort to do this sort of thing before?
I like quest's solution except that setting it should just be post.primary_category= and should take a category object. Just setup a has_one :primary_category on post and you're golden.
Short answer is to create a set_primary(post) method on category that takes as its argument the post and boolean.
def set_primary(post)
categorization = post.categorization.where('your opts hash here')
categorization.primary = true
categorization.save!
end
post.categories.first.set_primary(post)
Charlie Bowman does have the first piece of the puzzle with the set_primary logic on the Category model, however his setter doesn't unset the previous active category... In addition, the expensive part of Steve's problem, is actually just getting the primary category on each request. To get around that, I would also save the primary category id on the Post itself. That way you don't need to access the join model to figure out the primary category.
def set_primary_category(post)
post.categorizations.each do |cat|
if cat.post == post
cat.primary = true
cat.save!
post.update_attribute(:primary_category_id, cat.category_id)
else
cat.update_attribute(:primary, false) if cat.primary
end
end
end
TO SET:
post.categories.first.set_primary(post)
TO ACCESS:
post.primary_category
Unfortunately I don't think anyone has made a gem that makes this easier for you to accomplish, but the logic is pretty straightforward. It also has the benefit of having access to the primary state in both the categorization and the post, so that you always have a fast way to access the data.
Also, I think its good to remember. You have a many to many relationship for posts/categories. But only a has_one relationship for post/primary_category. Whenever you have an access pattern like that, I like to try and stay away from the join model for the has_one, since it just isn't needed.
this question is probably a duplicate of Ruby on Rails: attr_accessor for submodels, but basically delegate should be what you're looking for, with an additional callback on save.
I have a model called Contacts.
Contacts can have different status "bad, positive, wrong..."
These status may need to be changed over time, but across all contacts, they are the same options.
Should I model it this way:
Contacts.rb
belongs_to :status_contact
StatusContacts.rb
has_many :contacts
Then I manually populate the types of status in the table?
I then want to use Ajax to click a button corresponding to a value to update the value for Contacts.
It looks like you're trying to ensure that the values for your status are always going to restricted to a set of possible answers of your choosing. If that's all you're trying to do, there's no special need for a separate table. You can use the magic of ActiveRecord validations instead here.
First, create a string database column for Contact called :status.
Then you can use a validation to ensure that the values are limited to the ones you want. In Rails 3, you can do it like this:
validate :status, :inclusion => { :in => %w( bad positive wrong ) }
(If you're using Rails 2, use #validates_inclusion_of instead.)
In ActiveRecord, validations check that the object's values are valid before saving; it refuses to persist the object into the database until all validations pass.
Your naming strikes me as a little weird—ContactStatus sounds a little nicer to me—but I see this as being the general idea to achieve what you want.
No clear answer yet --- I think I need to use the table because it would allow the users to add and modify the types of status used across their application.
I'm working on my first Rails app. It needs to store some information about a single advertisement that will appear on every page. The admin just needs to be able to set the URL, Title, and an image. Obviously, I only need one instance of this ad object.
I created a model that inherits from ActiveRecod::Base, but that seems like the wrong thing to do, since it is configured to save multiple ads in a database table.
What's the best way for me to do this? What should the model and controller look like?
Thanks in advance,
Avi
A better way would be adding a validation that checks if one record entry already exists.
Inside your model:
validate :check_record, on: :create #please not that validate in this case is singular
def check_record
if Ad.all.count === 1
errors[:base] << "You can only have one active advertisement"
end
end
Well, if you are going to store the information (URL, Title, image) in the database, I think having it inheriting from AR is the right thing to do. If they are going to do this through any sort of front-end, I think this is your best option.
Why not allow for multiple advertisements, but only one of them can be published at a time? That way, you can also have a history of the advertisements—I don't know how important that part is, but it might prove interesting down the line.
I would suggest that you continue using ActiveRecord but that you add a boolean attribute that determines which of the many advertisement records is the active one. I have called this field active within the following example.
You can then validate within the model that a maximum of one record is active. The validation of the active attribute should succeed under any of the following conditions
The active attribute is set to false
There are 0 records with an active value of true.
The current record already has the attribute set to true in the database.
The following class should meet your needs
class Ad < ActiveRecord::Base
named_scope :has_active, :conditions => {:active => true}
def validate
errors.add_to_base "You can only have one active advertisement"
unless self.active_flag_valid?
end
def active_flag_valid?
self.active == false ||
Ad.has_active.size == 0 ||
( Ad.has_active.size == 1 && !self.active_changed?)
end
end
I agree. If you're completely sure it's static, then it doesn't even need to be stored in the database. If it does change, Rails will give you created_at and updated_at pretty much for free, so getting the most recently created / updated is one simple way to approach displaying what's current.
I'm running into problems implementing statuses for a model. This is probably due to wrong design.
There is a model which has a status. There can be multiple instances of the model and only a few predefined statuses (like: created, renewed, retrieved etc.). For each individual status there is some calculation logic for the model. E.g. model.cost() is differently calculated for each status.
I'd like to have ActiveRecord automatically set the correct model_status_id when saving a model. I think in the ideal situation I could do something like this:
model.status = StatusModel.retrieved
and
case status
when renewed
# ...
when retrieved
# ..
end
Thinking i need to save the status in the model row in the database this is what i've got now:
ModelStatus < ActiveRecord::Base
has_many :models
Model < ActiveRecord::Base
belongs_to :model_status
However this is giving me a lot of issues in the code. Anyone having some good ideas or patterns for this?
What you describe seems like a perfect case for a state machine.
There are many Ruby state machine implementations. You can see a fairly representative list at ruby-toolbox
When defining a state machine you can define several States and transitions. Each transitions taking your model from one state to another executing some code along the way. The DSL for it is usually quite nice.
Your example would look like
model.retrieve!
This will change the mode status from whatever it was to retrieved or throw an exception if current status doesn't transition to retrieved.
Why not keep the status part of the actual model? If they are predefined, that's not too much work:
class Model < ActiveRecord::Base
STAT_CREATED = 1
STAT_RENEWED = 2
STAT_RETRIEVED = 4
validates_inclusion_of :status,
:in => [1, 2, 4]
def created?
status & STAT_CREATED
end
def renewed?
status & STAT_RENEWED
end
def retrieved?
status & STAT_RETRIEVED
end
end
This way, you could either test the model instance directly (e.g. if #model.created?) or write your case statements like that:
case #model.status
when Model::STAT_CREATED
...
when Model::STAT_RENEWED
...
Also try taking a look at the acts_as_state_machine plugin. I recently used it on a project and it worked well.