Two Model Classes - One Database Table - ruby-on-rails

Do you ever do this?
I'm writing a Rails app. I have a situation where I have a Task model (and table), the Task has attributes, people that are allowed to view it, and a hierarchy (it may be under a project, or a business).
I also have an AssignmentController that exposes some views and functionality to the individual that's assigned to the Task - The AssignmentController uses Task.find to get the task and is the same object as the Task - it's just being updated by the Assignee and only a few columns are available to the assignee. In this case, I wanted to hide some UI, change the layout to fit the business, and the hierarchy didn't matter for the assignee of the task.
What I'm thinking of doing is creating a Task model and an Assignment model that both point at the same table (the task table). I don't see why I shouldn't do this. It would allow me to thin down the Assignment model class and isolate methods that are only used by the Task. it would also make much of the code cleaner, as far as I can tell.
I don't see much about this pattern when I search on the web. Any opinions about this?
Appreciate your thoughts...

If I understood correctly you want to subclass tables :)
Nice idea, don't see why it wouldn't work...
set_table_name "tasks" will do it, or just subclass the Task model.
But in both cases you'll get into trouble with the associations, you'll have to define belongs_to :task and belongs_to :assignment for any has_many association there it you want to do it right...
Edit:
Actually forget what I said :)
http://railscasts.com/episodes/154-polymorphic-association

You should have a join table between Assignee and Tasks. Your proposal would cause your views to be littered with if statements on weather or not you have an assignee or task. If you find that you are using similar chunks of html between the two, you can simply use partials.
Also, your proposal somewhat defies database normalization. Normalization isn't just what you need today, but what you might need tomorrow. If you have no more information on an assignee than a username it might be okay to have the same table for both models (kind of). But what about later when you want to have more information on Assignees?

Related

rails associations & object identity: maintaining object identity through a series of changes

`First of all, the fact that I am even asking this question implies that I am consciously choosing not to (strictly) obey the law of Demeter.
Since sometime (probably rails 3?) referring to model.association.first results in a new object each time, unless you use .to_a on the association:
campaign.campaign_shirts.first.to_s
=> "#<CampaignShirt:0x007fdd02c7fd58>"
campaign.campaign_shirts.first.to_s
=> "#<CampaignShirt:0x007fdd02ca6318>"
c.campaign_shirts.to_a.first.to_s
=> "#<CampaignShirt:0x007fdd02d13170>"
c.campaign_shirts.to_a.first.to_s
=> "#<CampaignShirt:0x007fdd02d13170>"
I've worked on several Rails 3/4 applications without even noticing this, probably because I do try to respect Demeter as much as is practical.
In this case I want Campaign to be in control, because it is a big state machine where many of its state changes involve transactionally coordinating changes in itself and various child objects.
Is there a way to freeze the association arrays at create and/or fetch time?
EDIT: I noticed almost immediately that they are frozen when you use Campaign.includes(...).find, which I am doing in my app. However I still have a problem in specs where the objects are factories created by FactoryGirl. Is there a way to say "freeze all the associations on this object" or do I have to call .to_a on each of them?
EDIT 2: I still have a problem when I refer to campaign through a belongs_to on user. (this seemed like a separate question, so I asked it here).
EDIT 3: the problem with the belongs_to includes extension was just syntax, so I'm removing the details of that.
So, my remaining problem is to get User.selected_campaign to act like it does in my app when it is built up by FactoryGirl. I'm going to try just doing a .reload at the start of each spec, which should trigger the includes extensions, at the cost of some spec performance.
I don't know the (gory) details of your setup, but what if you just memoize first on the Campaign object?
def Campaign
def first_campaign_shirt
#first_campaign_shirt ||= campaign_shirts.first
end
end
I think this way you obey the Law of Demeter again? But it might get annoying if you need more getters than just first_shirt. So consider this just a suggestion that won't fit in a comment box. :)
All of my in-app use cases were solved by carefully choosing where to add includes(..) to associations and scopes.
I was only able to solve the factory girl problem by calling reload on a factory object after creating it.

Should I define models that will be owned by many different models or use controller and views?

I am struggling to figure out how to get a relationship between several models. I have sales_leads which I need to view by company and by event. So, if someone looks up the company leads they can see everything across all events, but also see all leads by event. Not sure if this is ownership versus a where?
Should it be something like
Company.sales_leads where("event.event_id = ?", "2356")
Or Models:
sales_lead
belongs_to event
belongs_to company
Also, could I suggest you go right back to basics. First up, draw on a bit of paper precisely how you want the models to interact...
Do you want your model 'Foo' to have one or many 'Bars'? Or do you want Foo to have many Bars and Bars to have many Foos. If that makes sense!!
Don't write any code until you know what you exactly need.
Maybe start by watching a few of the following Railscasts:
http://railscasts.com/episodes/47-two-many-to-many
http://railscasts.com/episodes/17-habtm-checkboxes
http://railscasts.com/episodes/154-polymorphic-association-revised
http://railscasts.com/episodes/189-embedded-association

Lots of different models in tests (rspec)? Advanced

I am only looking for answers from senior/more experienced Ruby/Rails developers on this one, since I think this is a bit more advanced of a question.
I have a gem I am working on that adds some behavior to AR models. I have to test it for a lot of different associations (has_many, habtm, has_one etc), and I also have to test behavior when different options are passed for the associations (e.g. :foreign_key). Now with all these different models, I can use the same table in the database because the fields themselves do not need to change, only behavior specified through has_many, belongs_to and so on.
Keep in mind there are a lot of different options, so the number of models is quite large.
First I don't think it would be bad practice to have the definition of the models next to / in the test itself, for readability purposes (if I have multiple tests that use the same model then I would group them together and use the before method). So this is one of my goals, and you can comment on this if you don't agree.
The second thing I am not sure of is I wanted to keep the simple/same name of the model in all the tests, for example "Task", instead of TaskWithManySubtasksAndForeignKey or something ugly like that. The problem is there are so many models it's hard to come up with meaningful and simple names. I'm not quite sure about this - using the same name, since it's a constant, is a little problematic. I have a solution with a proxy class but I don't think this is the optimal solution. I was considering using variables (with the let method) like "taskModel", but it seemed a little verbose and unusual.
One other option that comes to mind, but I am not sure is possible to do easily, is to remove an existing association and then define a new one. So e.g. add a has_many and then remove it, add a habtm...
How would you go about doing this?
Defining unique models in the spec files is not necessarily a bad idea since it makes it easy to see exactly how each model is defined. The obvious problem with this approach is if you want to reuse the models in other test files. The Rails approach to this is to define all the models in separate files and then just require them in the tests that need it.
I think it really just depends on how many models you have and how much you want to reuse. In one of my gems, I took the approach of defining the models in the spec file, in another gem, I defined them in the spec helper, and in yet another I took the Rails approach and used a separate directory for them. If you asked me which one I preferred, I'd probably go with the spec that also contains the models because it's all in one place. Definitely a subjective problem though.
Another approach I've taken on occasion is to create an anonymous class that's guaranteed to only be around for the life of that test:
describe 'my test' do
let(:my_class) do
Class.new(Task) do
has_many :things
belongs_to :something_else
end
end
it 'should have many things' do
my_class.should have(100).things
end
end

Rails Multiple Table Inheritance question

I am starting to implement an MTI solution and have a basic question. I have 3 physical models - SMSNotifications, EmailNotifications, TwitterNotifications and they are subclasses of notification. At times in my code, I want to say Notifications.find(:all)so that I can get a set of results sorted by their creation time. Then I want to do things based on their subclass. What is the way to write Notifications.find(:all) and have Rails look through the subclass tables and combine the results? Right now Rails still thinks I have a physical Notifications table which goes against my MTI design.
I am also considering the possibility that I should be using STI instead. I would probably have 10 empty columns per row but if getting all notifications requires a query for each type of notification, then I feel like this could be a big issue.
Thanks!
Yes, you will need separate queries for each type. If this is a dealbreaker, then you should go with either STI or the mixed model approach advocated in your previous question.
If you use https://github.com/hzamani/acts_as_relation:
rails g model notification notification_type:string notification_id:integer <other columns>
rails g model email_notification <columns>
class EmailNotification < ActiveRecord::Base
acts_as :notification
end
....
then the code you want might be something like this:
Notification.all.each do |n|
case n.notification_type
when "EmailNotification"
...
when "SMSNotification"
...
end
end
For this purpose, you can make the subclasses have a polymorphic association with some specific model (X). You can edit the callback after_initialize to associate one entry of this specific model X with each of your subclasses on creation. In this way, you can perform find on the target (X) of the mentioned polymorphic association.
I recently forked a promising project to implement multiple table inheritance and class inheritance in Rails. I have spent a few days subjecting it to rapid development, fixes, commenting and documentation and have re-released it as CITIER Class Inheritance and Table Inheritance Embeddings for Rails.
I think it should allow you to do what you needed by asking for all notifications, it will still return the correct models. If you were to do something like
Notification.all().each do |n|
if n.class == 'EmailNotification'
#Do something
end
end
Or even define a function in the root Notification class and overload it in subclasses to return something different.
Consider giving it a look: http://peterhamilton.github.com/citier
I am finding it so useful! I would (by the way) welcome any help for the community in issues and testing, code cleanup etc! I know this is something many people would appreciate.
Please make sure you update regularly however because like I said, it has been improving/evolving by the day.

Calling ActiveRecord's #relationship_ids = [1,2,3] saves immediately. Any workarounds?

I've come across an oddity in ActiveRecord's #relationship_ids method (that's added automatically when you declare 'has_many'), which saves immediately for existing records, which is causing me some issues, and I wonder if anyone had any useful advice.
I'm running Rails 2.3.5.
Consider this simple scenario, where an article has_many tags, say:
a = Article.first
a.name = "New Name" # No save yet
a.author_id = 1 # No save yet
a.tag_ids = [1,2,3] # These changes are saved to the database
# immediately, even if I don't subsequently
# call 'a.save'
This seems surprising to me. It's specifically causing problems whilst trying to build a preview facility - I want to update a bunch of attributes and then preview the article without saving it - but in this instance the tag changes do get saved, even though no other fields do.
(Of possible relevance is that if 'a' is a new article, rather than an existing one, things behave as I'd expect - nothing is saved until I call 'a.save')
I have a fairly nasty workaround - I can override the tag_ids= method in my model to instead populate an instance variable, and actually save the related models in a before_save callback.
But I'd love to know of a simpler way than me having to do this for every model with a has_many relationship I'd like to create a preview facility for.
Does anyone have any fixes/workarounds/general advice? Thanks!
There's a reason things are this way. It's called foreign keys. In a has many relationship, the information that links to the model that has many is stored outside of that model as a foreign key.
As in Articles, has many tags. The information that links a tag to an article is stored either in the tags table or in a join table. When you call save on an article you're only saving the article.
Active record modifies those other records immediately. Except in the case where you're working with a new article that hasn't been saved yet. Rails will delay creating/updating the associated records if it doesn't know which id to place in the foreign key.
However, if you're modifying existing records, the solution you've decided on is really all that you can do. There's an even uglier hack using accepts_nested_attributes_for, but it's really not worth the effort.
If you're looking to add this behaviour to many models but not all models, you might want to consider writing a simple plugin to redefine the assigment the method you need and add the call back in a single class method call. Have a look at the source of something like acts_as_audited to see how it's done.
If you're looking to add this behaviour to all models, you can probably write a wrapper for has_many to do that.

Resources