I have a question about rails and how its relationships query builder, specifically how camel case is converted for the related calls.
Relevant Code
class CustomerPlan < ActiveRecord::Base
attr_accessible :customer_id, :plan_id, :startDate, :user_id
has_many :planActions
end
class PlanAction < ActiveRecord::Base
attr_accessible :actionType_id, :customerPlan_id, :notes, :timeSpent
belongs_to :customerPlan
belongs_to :actionType
end
The getters and setters work just fine, such as plan_action.actionType.name will correctly pull from the related model. However customer_plan.planActions.each returns the error:
SQLite3::SQLException: no such column: plan_actions.customer_plan_id:
SELECT "plan_actions".*
FROM "plan_actions"
WHERE "plan_actions"."customer_plan_id" = 1
The column is defined in the database as customerPlan_id, was I just wrong to use this? It works for every other call, all my other relationships work fine. Even PlanAction -> CustomerPlan.
I ran through all the docs, and searched about every other source I know of. It would be simple enough to change my columns, I just want to know what's going on here.
Thank you for your time!
A quick fix for this is to just explicitly set the foreign_key.
has_many :planActions, :foreign_key => "customerPlan_id", :class_name => "PlanAction"
Still, I think I am missing some model naming convention somewhere, just can't seem to figure out what.
The Rails convention for DB column names is to use lowercase letters with words separated by an underscore (e.g. author_id, comments_count, updated_at, etc).
I would highly recommend that you stick to the Rails conventions. This would make your life much easier. To change it to the rails convention, simply create a migration to rename the column to the appropriate style.
However, if you do want to use a custom style for the column name, rails provides the :foreign_key option in the has_many relationship to specify the expected foreign column name:
class CustomerPlan < ActiveRecord::Base
has_many :plan_actions, :foreign_key => 'customerPlan_id'
end
You can also use the alias_attribute macro to alias the column name, if you'd like to use a different model attribute name than the actual DB column name. But as I mentioned, I would recommend sticking to the rails convention as much as possible. You'll thank me later.
Rails has 3 basic naming schemes.
One is for constants, and it is ALL_UPPERCASE_SEPARATED_BY_UNDERSCORES.
One is for Classes and it is AllCamelCaseWithNoUnderscores.
One is for variables and method names, and is all_lowercase_separated_by_underscores.
The reason that it is this way is not just for consistency, but also because it freely converts between them using these methods.
So, to make your posted code more rails-y:
class CustomerPlan < ActiveRecord::Base
attr_accessible :customer_id, :plan_id, :start_date, :user_id
has_many :plan_actions
end
class PlanAction < ActiveRecord::Base
attr_accessible :action_type_id, :customer_plan_id, :notes, :time_spent
belongs_to :customer_plan
belongs_to :action_type
end
Related
I have the following models
class Widget
has_one :widget_sprocket
has_one :sprocket, through: :widget_sprockets
end
class Sprocket
has_many :widget_sprockets
has_many :widgets, though: :widget_sprockets
end
class WidgetSprocket
belongs_to :widget
belongs_to :sprocket
end
This works fine in the console, but I'm struggling with view updates for Widget. has_many :through gives Sprocket widget_ids, which I believe can be treated like a local attribute for most purposes, but the Rails docs evidently expect a different table configuration for has_one :through and therefore doesn't define a sprocket_id on Widget. As a result code like this throws an unknown attribute error
<%= f.collection_select(:sprocket_id, Sprocket.all, :id, :sprocket_type) %>
Of course I could use has_many :through for both models, but I consider it a last resort.
I think you're falling for a classic trap and overcomplicating this. If you want a one to many assocation between Sprocket and Widget you should just be using belongs_to and adding a sprocket_id foreign key column to the widgets table:
class AddSprocketToWidgets < ActiveRecord::Migration[6.1]
def change
add_reference :widgets, :sprocket, null: false, foreign_key: true
end
end
class Widget
belongs_to :sprocket
end
This guarentees on the database level that a Widget can only have one Sprocket because the column can only hold one single value. Your join table gives no such guarentee. You're really just selecting the first matching row off the join table and its actually a many to many relation. Unless thats acceptable or you prevent it with unique indexes thats an invitation for some nasty bugs.
While there are scenarios where you actually need an intermediadary table that describes a one to many relation - YAGNI.
has_many :through gives Sprocket widget_ids, which I believe can be treated like a local attribute for most purposes
Its not an attribute in any way or form. Its a method which will actually do a SELECT id FROM other_tablequery unless the assocation is preloaded.
but the Rails docs evidently expect a different table configuration for has_one :through and therefore doesn't define a sprocket_id on Widget.
Classic noob trap caused by the confusing semantics of the method names. has_one means there is a foreign key column on the other models table. Its like has_many but with a LIMIT 1 tacked onto the end of the query. To get the id you would actually call other.id.
In the case of belongs_to its not the relations macro that creates the attribute. Its having an actual sprocket_id column on the widgets table.
If you actually wanted to go though creating an intermediary table you can't just assign an id. You would have to use nested attributes and fields_for to create or update a WidgetSprocket instance. Again YAGNI.
I have a Totem model and Totems table.
There will be many totems and I need to store the order of the totems in the database table.
I added a previous_totem_id and next_totem_id to the Totems table to store the order information. I did it via this
Rails Migration:
class AddPreviousNextTotemColumnsToTotems < ActiveRecord::Migration
def change
add_column :totems, :previous_totem_id, :integer
add_column :totems, :next_totem_id, :integer
end
end
Now in the Model I have defined the relationships:
class Totem < ActiveRecord::Base
validates :name, :presence => true
has_one :previous_totem, :class_name => 'Totem'
has_one :next_totem, :class_name => 'Totem'
end
I created a couple of these totems through ActiveRecord and tried to use the previous_totem_id column like so:
totem = Totem.create! name: 'a1'
Totem.create! name: '1a'
totem.previous_totem_id = Totem.find_by(name: '1a').id
puts totem.previous_totem #This is NIL
However, the previous_totem comes back as nil, and I do not see a select statement in the mysql log when calling this line
totem.previous_totem
Is this relationship recommended? What is the best way to implement a self referencing column?
Changing direction of the association from has_one to belongs_to and specifying foreign keys, should make your code work as you expected:
class Totem < ActiveRecord::Base
validates :name, :presence => true
belongs_to :previous_totem, :class_name => 'Totem', foreign_key: :previous_totem_id
belongs_to :next_totem, :class_name => 'Totem', foreign_key: :next_totem_id
end
However, good association should be properly named and declared on both sides - with matching has_one association; in this case it's impossible without naming conflicts :) Self join might be sometimes useful, but i'm not sure if it's the best solution here. I didn't use the gem moveson recommends, but an integer column to store position is something I use and IMHO makes reordering records easier :)
If the only reason for the self-reference is to store the order of the totems, please don't do it this way. It's your lucky day: This is a solved problem!
Use a position field and the acts_as_list gem, which will take care of this problem for you in a neat and performant way.
I'm having the following problem: i have a lot of tables, some of then are nested and other aren't. I divided my app in some areas that i think that is the correct way. One area should manipulate some tables at the same time, at the same action, like i said: some models are nested others not. What is the best solution in Rails world?
What should i read to get the point?
I tried to use
accepts_nested_attributes_for
and i tried build the objects to use at the same form with fields_for. But it's going to be a complex form because some objects contains foreign keys and unfortunately i can't get the correct builds with more than 2 objects.
I'll keep trying.
Thanks!
----edit-----
Class Country < ActiveRecord::Base
has_many :states
attr_accessible :nome
# i tried # accepts_nested_attributes_for :state
end
Class State < ActiveRecord::Base
belongs_to :country
has_many :cities
attr_accessible :nome, :country_id
# i tried # accepts_nested_attributes_for :city
# i tried # accepts_nested_attributes_for :country # too
end
The models continues until we get the adress model:
Class Adress < ActiveRecord::Base
has_many :bairros_logradouros # we name streets, avenues, parks as logradouros
# here in Brasil, the others models are translated
# to EN here
has_many :logradouros, :trough => :bairros_logradouros # many-to-many
attr_accessible :number, :complement, :other, :another
# i tried # accepts_nested_attributes_for :logradouro
end
The setup: Country -> State -> City -> District(bairro, here) -> :Logradouro <-> Adress.
I tried to build the chain in both direction, but i only get 2 objects, the third brings a nil problem with the build method.
These tables are about adress, i should manipulate the User model tha has_one Person that, at the end has an adress, i would like to point the adress at the :addres_id inside Person.
All this must be manipulated at the custom Data controller, all CRUDs here.
I couldn't build the entire chain:
#addres = #addres.new
#other = #addres.logradouros.build
#another = #other.build_district
#even_more = #another.build_city
....
I learned to use objects.build and build_object, but i couldn't build more than 2 nested objects.
I'm a newbie.
Thanks again!
Here is a great place to start:
http://www.tutorialspoint.com/ruby-on-rails/index.htm
If you are using mass assignment whitelisting in Rails 3.1+ (up to but not including Rails 4) then if you use accepts_nested_attributes_for :logradouros you need to have attr_accessible :logradouros_attributes (notice the plural logradouros and the _attributes in the name). But, you may have more problems than that. Here is a related question.
A good reference when starting out is to read everything in the Rails guide.
I have a table with id|patient_id|client_id|active. A record is unique by patient_id, client_id meaning there should only be one enrollment per patient per client. Normally I would make that the primary key, but in rails I have id as my primary key.
What is the best way to enforce this? Validations?
Sounds like you have a model relationship of:
class Client < ActiveRecord::Base
has_many :patients, :through => :enrollments
has_many :enrollments
end
class ClientPatient < ActiveRecord::Base
belongs_to :client
belongs_to :patient
end
class Patient < ActiveRecord::Base
has_many :clients, :through => :enrollments
has_many :enrollments
end
To enforce your constraint I would do it in ActiveRecord, so that you get proper feedback when attempting to save a record that breaks the constraint. I would just modify your ClientPatient model like so:
class Enrollment < ActiveRecord::Base
belongs_to :client
belongs_to :patient
validates_uniqueness_of :patient_id, :scope => :client_id
end
Be careful though because, while this is great for small-scale applications it is still prone to possible race conditions as described here: http://apidock.com/rails/v3.0.5/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of under "Concurrency and Integrity"
As they describe there, you should also add a unique index to the table in the database. This will provide two immediate benefits:
The validation check and any searches through this model based on these two id's will perform faster (since they're indexed)
The uniqueness constraint will be enforced DB-side, and on the rare occurrence of a race condition you won't get bad data saved to the database... although users will get a 500 Server Error if you don't catch the error.
In a migration file add the following:
add_index :enrollments, [:patient_id, :client_id], :unique => true
Hopefully this was helpful :)
Edit (fixed some naming issues and a couple obvious bugs):
It's then very easy to find the data you're looking for:
Client.find_by_name("Bob Smith").patients
Patient.find_by_name("Henry Person").clients
Validations would work (Back them up with a unique index!), but there's no way to get a true composite primary key in vanilla Rails. If you want a real composite primary key, you're going to need a gem/plugin - composite_primary_keys is the one I found, but I'm sure there are others.
Hope this helps!
Add a UNIQUE constraint to your table across the two columns. Here's a reference for MySQL http://dev.mysql.com/doc/refman/5.0/en/constraint-primary-key.html
See comments for updates.
I've been struggling to get a clear and straight-forward answer on this one, I'm hoping this time I'll get it! :D
I definitely have a lot to learn still with Rails, however I do understand the problem I'm facing and would really appreciate additional help.
I have a model called "Task".
I have an abstract model called "Target".
I would like to relate multiple instances of subclasses of Target to Task.
I am not using single table inheritance.
I would like to query the polymorphic relationship to return a mixed result set of subclasses of Target.
I would like to query individual instances of subclasses of Target to obtain tasks that they are in a relationship with.
So, I figure a polymorphic many to many relationship between Tasks and subclasses of Targets is in order.
In more detail, I will be able to do things like this in the console (and of course elsewhere):
task = Task.find(1)
task.targets
[...array of all the subclasses of Target here...]
But! Assuming models "Store", "Software", "Office", "Vehicle", which are all subclasses of "Target" exist, it would be nice to also traverse the relationship in the other direction:
store = Store.find(1)
store.tasks
[...array of all the Tasks this Store is related to...]
software = Software.find(18)
software.tasks
[...array of all the Tasks this Software is related to...]
The database tables implied by polymorphic relationships appears to be capable of doing this traversal, but I see some recurring themes in trying to find an answer which to me defeat the spirit of polymorphic relationships:
Using my example still, people appear to want to define Store, Software, Office, Vehicle in Task, which we can tell right away isn't a polymorphic relationship as it only returns one type of model.
Similar to the last point, people still want to define Store, Software, Office and Vehicle in Task in one way shape or form. The important bit here is that the relationship is blind to the subclassing. My polymorphs will initially only be interacted with as Targets, not as their individual subclass types. Defining each subclass in Task again starts to eat away at the purpose of the polymorphic relationship.
I see that a model for the join table might be in order, that seems somewhat correct to me except that it adds some complexity I assumed Rails would be willing to do away with. I plea inexperience on this one.
It seems to be a small hole in either rails functionality or the collective community knowledge. So hopefully stackoverflow can chronicle my search for the answer!
Thanks to everyone who help!
You can combine polymorphism and has_many :through to get a flexible mapping:
class Assignment < ActiveRecord::Base
belongs_to :task
belongs_to :target, :polymorphic => true
end
class Task < ActiveRecord::Base
has_many :targets, :through => :assignment
end
class Store < ActiveRecord::Base
has_many :tasks, :through => :assignment, :as => :target
end
class Vehicle < ActiveRecord::Base
has_many :tasks, :through => :assignment, :as => :target
end
...And so forth.
Although the answer proposed by by SFEley is great, there a some flaws:
The retrieval of tasks from target (Store/Vehicle) works, but the backwards wont. That is basically because you can't traverse a :through association to a polymorphic data type because the SQL can't tell what table it's in.
Every model with a :through association need a direct association with the intermediate table
The :through Assignment association should be in plural
The :as statement wont work together with :through, you need to specify it first with the direct association needed with the intermediate table
With that in mind, my simplest solution would be:
class Assignment < ActiveRecord::Base
belongs_to :task
belongs_to :target, :polymorphic => true
end
class Task < ActiveRecord::Base
has_many :assignments
# acts as the the 'has_many targets' needed
def targets
assignments.map {|x| x.target}
end
end
class Store < ActiveRecord::Base
has_many :assignments, as: :target
has_many :tasks, :through => :assignment
end
class Vehicle < ActiveRecord::Base
has_many :assignments, as: :target
has_many :tasks, :through => :assignment, :as => :target
end
References:
http://blog.hasmanythrough.com/2006/4/3/polymorphic-through
The has_many_polymorphs solution you mention isn't that bad.
class Task < ActiveRecord::Base
has_many_polymorphs :targets, :from => [:store, :software, :office, :vehicle]
end
Seems to do everything you want.
It provides the following methods:
to Task:
t = Task.first
t.targets # Mixed collection of all targets associated with task t
t.stores # Collection of stores associated with task t
t.softwares # same but for software
t.offices # same but for office
t.vehicles # same but for vehicles
to Software, Store, Office, Vehicle:
s = Software.first # works for any of the subtargets.
s.tasks # lists tasks associated with s
If I'm following the comments correctly, the only remaining problem is that you don't want to have to modify app/models/task.rb every time you create a new type of Subtarget. The Rails way seems to require you to modify two files to create a bidirectional association. has_many_polymorphs only requires you to change the Tasks file. Seems like a win to me. Or at least it would if you didn't have to edit the new Model file anyway.
There are a few ways around this, but they seem like way too much work to avoid changing one file every once in a while. But if you're that dead set against modifying Task yourself to add to the polymorphic relationship, here's my suggestion:
Keep a list of subtargets, I'm going to suggest in lib/subtargets formatted one entry per line that is essentially the table_name.underscore. (Capital letters have an underscore prefixed and then everything is made lowercase)
store
software
office
vehicle
Create config/initializers/subtargets.rb and fill it with this:
SubtargetList = File.open("#{RAILS_ROOT}/lib/subtargets").read.split.reject(&:match(/#/)).map(&:to_sym)
Next you're going to want to either create a custom generator or a new rake task. To generate your new subtarget and add the model name to the subtarget list file, defined above. You'll probably end up doing something bare bones that makes the change and passes the arguments to the standard generator.
Sorry, I don't really feel like walking you through that right now, but here are some resources
Finally replace the list in the has_many_polymorphs declaration with SubtargetList
class Task < ActiveRecord::Base
has_many_polymorphs :targets, :from => SubtargetList
end
From this point on you could add a new subtarget with
$ script/generate subtarget_model home
And this will automatically update your polymorphic list once you reload your console or restart the production server.
As I said it's a lot of work to automatically update the subtargets list. However, if you do go this route you can tweak the custom generator ensure all the required parts of the subtarget model are there when you generate it.
Using STI:
class Task < ActiveRecord::Base
end
class StoreTask < Task
belongs_to :store, :foreign_key => "target_id"
end
class VehicleTask < Task
belongs_to :vehicle, :foreign_key => "target_id"
end
class Store < ActiveRecord::Base
has_many :tasks, :class_name => "StoreTask", :foreign_key => "target_id"
end
class Vehicle < ActiveRecord::Base
has_many :tasks, :class_name => "VehicleTask", :foreign_key => "target_id"
end
In your databse you'll need:
Task type:string and Task target_id:integer
The advantage is that now you have a through model for each task type which can be specific.
See also STI and polymorphic model together
Cheers!
This may not be an especially helpful answer, but stated simply, I don't think there is an easy or automagic way to do this. At least, not as easy as with simpler to-one or to-many associations.
I think that creating an ActiveRecord model for the join table is the right way to approach the problem. A normal has_and_belongs_to_many relationship assumes a join between two specified tables, whereas in your case it sounds like you want to join between tasks and any one of stores, softwares, offices, or vehicles (by the way, is there a reason not to use STI here? It seems like it would help reduce complexity by limiting the number of tables you have). So in your case, the join table would also need to know the name of the Target subclass involved. Something like
create_table :targets_tasks do |t|
t.integer :target_id
t.string :target_type
t.integer :task_id
end
Then, in your Task class, your Target subclasses, and the TargetsTask class, you could set up has_many associations using the :through keyword as documented on the ActiveRecord::Associations::ClassMethods rdoc pages.
But still, that only gets you part of the way, because :through won't know to use the target_type field as the Target subclass name. For that, you might be able to write some custom select/finder SQL fragments, also documented in ActiveRecord::Associations::ClassMethods.
Hopefully this gets you moving in the right direction. If you find a complete solution, I'd love to see it!
I agree with the others I would go for a solution that uses a mixture of STI and delegation would be much easier to implement.
At the heart of your problem is where to store a record of all the subclasses of Target. ActiveRecord chooses the database via the STI model.
You could store them in a class variable in the Target and use the inherited callback to add new ones to it. Then you can dynamically generate the code you'll need from the contents of that array and leverage method_missing.
Have you pursued that brute force approach:
class Task
has_many :stores
has_many :softwares
has_many :offices
has_many :vehicles
def targets
stores + softwares + offices + vehicles
end
...
It may not be that elegant, but to be honest it's not that verbose, and there is nothing inherently inefficient about the code.