Is there a way to delegate methods to the 'active' join model? - ruby-on-rails

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.

Related

How do I associate two entries in a database that are connected through a many-to-many relationship in Rails?

How do I associate two entries in a database that are connected through a many-to-many relationship in Rails?
I'm trying to associate Users and Issues for an issue tracker. I'm using has_and_belongs_to_many, not :through. I have a :user_id and :issue_id available to me, but there doesn't seem to be User.issues.find(id) or Issue.users.find(id) available to me. I have a route post "/", to: "home#create". I'm trying to make a create method in home_controller.rb.
From the look of it you're calling the method on the User class and not an instance.
If you want to get the issues connected to a user you need to fetch the user first:
User.find(id).issues
If you want to add a record to the association you can use the shovel method or any of the methods generated by the association macro:
User.find(id).issues << Issue.find(3)
User.find(id).issues.push(Issue.find(3))
User.find(id).issue_ids = [1, 2, 3]
Besides that you have a smattering of naming issues in your schema. Use snake_case everywhere in your database schema unless you have a good reason why you want to break the conventions and feel like explicitly configuring table and foreign key names.
I would also really question if you really want to use has_and_belongs_to_many. It should only really be used if you can't foresee that you ever will need to add additional attributes to the join table or never need to query the table directly - it seems pretty unrealistic that that would be true in an issue tracker. You want has_many through: - pretty much always.
I have a route post "/", to: "home#create". I'm trying to make a
create method in home_controller.rb.
Don't throw everything into a junk drawer controller. Think about your app in terms of resources that can be CRUD:ed and create controllers that handle just that resource. You should think about what the relation between a user and an issue is in your domain and how you can model it as an actual entity in the domain logic instead of just plumbing.
Maybe all I need to do is direct you to Rails Guides: Active Record Associations.
There is neither of these
User.issues.find(id)
Issue.users.find(id)
because when you are finding an issue or user by id, you don't use the association. Instead use these:
Issues.find(id)
Users.find(id)
Since the :id is unique this will work and should be what you want.
The only time you want to query issues or users using the association will be when you have the data for the other end of the relationship.
user = User.find(user_id)
issue = user.issues.where(id: issue_id)
Since the :id field is unique, this is the same as Issues.find(id). However if you want to get a collection of a user's issues with some other data, you can put the condition for that data in the where.
You can create an issue for a user this way:
user = User.find(user_id)
issue = User.issues.create( ... )

Is it possible to find all association records to an object for consolidating records in Rails 4

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.

Rails Active Record Associations

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

Rails 3.1 nested resource with composite primary key (id + parent_id)

Let's assume I need two resources nested in the routes.rb as follows:
resources :post do
resources :comment
end
By convention comments.id will be the comments primary key and comments.post_id will be a foreign key.
I need the primary key to be the composite key [comments.post_id, comments.id].
So that I could have every first comment of each distinct post with id == 1, every second comment of each distinct post with id == 2 and so on...
Of course I also need to disable every route that refers to a comment (the child resource) without referring also to its post (the parent resource).
This is just an example, my actual project isn't about a blog (I'd handled this problem differently), I'd like to know if there is a way to achieve such a behavior for a nested resource in order to achieve compatibility with a legacy database.
Thank you.
One way to do this is by creating another column (leave the posts.id as it was primary key), add an uniqueness validation on that column with scope to the post id, then write some before or after _create hood to generate that column value.
An example (not real code)
class Comment < ActiveRecord::Base
...
validates_uniqueness_of :sub_id, :scope => :post_id
before_create do
this.sub_id = post.comments.size + 1
end
end
So the sub_id column acts as a primary key. When you query a comment for a certain post, you do this:
post.comments.where(:sub_id => val).first
or
post.comments.find_by_sub_id(val)
Please be noted the real logic here should be adjusted to satisfy your requirement. For instance, if comments can be deleted, it might be a good idea to keep an counter on post which will used to determine the next sub_id (or write a sub id generator class).
Actually I am not quite sure what you are trying to accomplish and why? Maybe you cold make that a bit clearer. Anyway two links which might help:
Composite Primary Keys in Rails,
Rails Associations
So, if you can I would implement that using a third model as explained in the second link above. If thats is not possible, you might want to try them gem mentioned in the first link.
Just a side note: Probably logic a la
before_create do
this.sub_id = post.comments.size + 1
end
should be backed up by suitable handling of deleting comments. Otherwise we would soon run into duplicate sub_ids.

With Rails 2.x, how do I handle a table with a "valid" column?

I've got a table that includes a column named "valid". This has caused a problem after updating to Rails 2. ActiveRecord is expecting "def valid?" to do validation, not return a boolean value from the database.
How do I work around this problem? Is renaming the column my only option?
As documented elsewhere, there are things you can do, but I'm going to suggest that they're probably going to be more trouble in the long run than biting the bullet and renaming the column.
If your database is not open to other apps, that is - otherwise you're just going to suffer to some extent whatever you do...
Why rename? One of the greatest benefits that we get from Rails is convention over configuration. The "magic", if you will. (Some say that it's actually a bad thing, but go with me one this). If you retain a column named "valid", then nyou're making your models inconsistent: this one needs to work differently from the others and that's bad. Or you could monkey-patch ActiveRecord::Base perhaps, so then all your models work the same but your app no longer follows convention.
From personal experience: I created a column named "user_id" which ActiveRecord, by convention, considered a foreign key (as it does anything ending in "_id"). I coded around it, which I now think was a mistake. Another item on the to-do list...
It's not necessarily wrong to go against Rails conventions: there are plenty of places where you can do so and they're well-documented. On the ActiveRecord side, many are specifically designed to reduce difficulty in connecting to legacy database schemas, for example. Take a good look at the pros and cons, as you're obviously doing, and weigh up your options.
I can prevent the crash by adding the following to my model, but it's not entirely satisfactory:
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'valid?'
super
end
end
Do you need to see the column in your model? If not, overriding ActiveRecord::Base.columns will do the trick...
def self.columns
super.delete_if {|c| c.name == 'valid' }
end
You can access the attribute through the [] notation:
row[:valid] = "foo"
You'll get the DangerousAttributeError if you try to initialize an object like this:
row = MyModel.new :valid => "foo"
To prevent that, you can define an attribute setter for valid, like this:
def valid=(x)
self[:valid] = x
end
The valid? method will still be for row validation. You could define a different question method, like val? to get at the boolean, like this:
def val?
query_attribute('valid')
end
Now you can use row.val? to test the boolean

Resources