How to set composite key in Rails application - ruby-on-rails

I am using 'foriegner' gem in my application to set primary key, now i am in a situation to
set composite primary key in a table.
I searched the web for this, but could not able to find a clear solution for this.
Please suggest me if its possible to set composite keys in rails application using foreigner gem or by any other means.
Note : I am using Postgres
thanks.

ActiveRecord
There is a gem called composite_primary_keys and integrates these features quite will for ActiveRecord.
It supports a wide array of ActiveRecord versions.
# Gemfile
gem 'composite_primary_keys', '=<version for your AR version>'
Use it like the following example:
class Membership < ActiveRecord::Base
self.primary_keys = :user_id, :group_id
end
membership = Membership.find([1,1]) # composite ids returns single instance
# => <Membership:0x39218b0 #attributes={"user_id"=>"1", "group_id"=>"1"}>
I am happily using it in production, keeping my schema clean and tidy.
Better alternative: Sequel
As always, if you are looking for a great Ruby + PostgreSQL solution, look no further than jeremyevans/sequel.
It comes with a vast amount of features, including composite primary keys. Out of the box.
So if you are starting a new project (on PostgreSQL obviously), save yourself the headache of ActiveRecord and go with Sequel.
class Post < Sequel::Model
set_primary_key [:category, :title]
end

Short answer: DON'T use composite PKs in Rails (unless you are forced to do so because of a legacy DB).
While there might be solutions with gems that work (currently) this is just not the way that ActiveRecord works. I'd also not use composite PKs outside of Rails unless there is a really good reason. They make a lot of things more complicated.
But: nothing prevents you from having what you think of a a composite primary key as an alternative key and have the Rails default PK (postgres: pseudotype 'serial' which draws its values from a sequence). Just make sure that you add a unique index for those columns that make up your key.

Related

Possible to use `attribute` with `store_accessor`

In Rails 5, is it possible to use the new attributes API with a field exposed via store_accessor on a jsonb column?
For example, I have:
class Item < ApplicationRecord
# ...
store_accessor :metadata, :publication_date
attribute :publication_date, :datetime
end
Then I'd like to call i = Item.new(publication_date: '2012-10-24'), and have metadata be a hash like: { 'publication_date' => #<DateTimeInstance> }.
However, the attribute call doesn't seem to be doing any coercion.
Hopefully I am missing something--it seems like being able to use these two features in conjunction would be very useful when working with jsonb columns. (Speaking of which, why doesn't the attributes API expose a generic array: true option? That would also be very useful for this case.)
I see that there is a project jsonb_accessor, but it seems a little heavyweight. It also seems to be designed for Rails 4 (I haven't checked whether it supports Rails 5).
You might check out a rather new (as of this writing) gem built atop the Rails 5+ Attributes API: AttrJson. I've recently started using it; some rough edges still, but the author/maintainer seems keen to improve it.
After digging in a little more, I see that the Attributes API (as it currently exists in ActiveRecord) is not really appropriate for handling jsonb data—there would be duplicate info in the attributes hash, etc.
I do think it would be nice if ActiveRecord provided typecasting/coercion for jsonb fields. I see that there is a project jsonb_accessor, but it seems a little heavyweight. It also seems to be designed for Rails 4 (I haven't checked whether it supports Rails 5).
I guess something like this might be in the works for Rails since the ActiveRecord::Type values are actually defined in ActiveModel.
For now I am using the following. I've never really loved Hashie, but this is relatively lightweight and easy to use:
class Item < ApplicationRecord
class Metadata < Hashie::Dash
include Hashie::Extensions::Dash::Coercion
include Hashie::Extensions::Dash::IndifferentAccess
property :publication_date, coerce: Time
def self.dump(obj); obj.as_json; end
def self.load(obj); new(obj); end
end
serialize :metadata, Metadata
store_accessor :metadata, :publication_date
end

Why would someone use the gem foreigner?

This is most likely a noob question since people use this gem and a lot of people love it, but I don't get the purpose. I'm looking at a project and its been used here many times in places such as t.references :foreign_key_table_name , :foreign_key => true, add_foreign_key :table :foreign_key_table_name, :options, and in a create t.foreign_key :foreign_key_table_name. Hope those weren't confusing since they're out of context.
But I don't get how this is different from what rails does built in with t.references :foreign_key_table_name or from me just adding t.integer :foreign_key_table_name_id? does it simply make it more readable by making clear that this is a 'foreign key'? I could just add a comment instead of a gem if thats the case... The only advantage I see is that you can move options such as :dependent into the migration instead of having it in the model, but who cares?
Some database engines support legit foreign key constraints: if someone tries to save a Child with a parent_id of 5, but there's no Parent with id 5, then the database itself (not Rails) will reject the record if there's a foreign key constraint linking children.parent_id and parents.id.
A foreign key can also specify what happens if the parent is deleted: in MySQL, for example, we can delete or nullify the dependent records, like how Rails does with :dependent, or even just straight-up reject the deletion and throw an error instead.
Since not all database engines offer this functionality, Rails offers to emulate it with :dependent, and it's nice to have it on the software level so that dependent child records can fire their destroy callbacks when the parent is deleted. Since the feature is engine-independent and therefore pretty much schema-independent, Rails doesn't handle the creation/deletion of foreign keys. That's where foreigner comes in: if your engine supports foreign key constraints, and you want that extra confident in your data integrity, foreigner can help with that.
Resurrecting an old question here, but…
Having rails enforce the relationship is fine, within rails itself.
However, if your project grows to have code that also accesses these tables from other languages, that will not have the benefit of rails enforcing the relations. These foreign key constraints are baked into the SQL tables themselves, so can protect non-rails code.
This will also protect you if you need to perform datafixes or otherwise manipulate your data via native SQL.
Another reason is that some documentation tools for SQL look at foreign keys on the DB, so it is cool to have a gem that generates them. Rails 4 added the ability to define foreign keys in the same migration that creates the table with:
t.references :something, foreign_key: true
And the generators will do this for you if you use the references type. Rails adds an index on something_id by default when using foreign_key like this

Placing the foreign key in rails model belongs_to association

I am a beginner to rails framework. I have a fundamental question.
I am trying to define some models and their association referring the popular rails guides. My association looks like below.
class Person < ActiveRecord::Base
has_one :head
end
class Head < ActiveRecord::Base
belongs_to :person
end
Here, I need to add the foreign_key (Persons's primary key) in the table 'head'.
Now, if I need to get the 'head' of a 'person', rails need to scan through the head table and match the person_id.
The straight forward way I would think is to add the foreign key in 'person' table. Then I can directly refer the 'head' from 'person' with it's ID.
It appears that rails convention is not performance friendly. Am I missing something here?
When you create the migration to add the column containing the foreign key, it is highly recommended to add an index on this column. This way, the database will efficiently find the Head from the person_id (as efficiently then a search by its id).
add_index :heads, :person_id
If it's a one-to-one association, you can even add the unique option (unless your application accepts conjoined twins :-) ):
add_index :heads, :person_id, :unique => true
I suggest you to have a look to this 2 articles on where to use indexes:
http://tomafro.net/2009/08/using-indexes-in-rails-index-your-associations
http://tomafro.net/2009/08/using-indexes-in-rails-choosing-additional-indexes
Re: It appears that rails convention is not performance friendly. Am I missing something here?
Yes. Rails is designed for production sites. So it includes many performance features. As #Baldrick says, the answer to your specific concern is to always add an index for foreign key fields.
Adding an index for each foreign key field is needed, for performance reasons, for any SQL dbms application, no matter the language. Note that the index is added to the database, not to the MVC layers (Rails).
Rails itself includes additional performance features including sql results caching, optional fragment and page caching, and more.
Rails 3.2 includes the slow query features. These enable Rails to automatically show you the queries which are slow. You can then focus on fixing them as appropriate.

Creating readable models in rails

I have just started with Rails and coming from a .net background I find the model inheriting from ActiveRecord is hard to understand, since the don't contain the corresponding attributes for the model. I cannot imagine a new developer exposed to a large code where the models only contains references to other models and business logic.
From my point of view the DataMapper model is much easier to grasp but since ActiveRecord is the defacto standard it feels weird to change the ORM just for this little problem.
DataMapper
class Post
include DataMapper::Resource
property :id, Serial # An auto-increment integer key
property :title, String # A varchar type string, for short strings
property :body, Text # A text block, for longer string data.
property :created_at, DateTime # A DateTime, for any date you might like.
end
ActiveRecord
class Post < ActiveRecord::Base
end
I'm not sure if this is an issue and that people get used to the models without attributes, or how does experienced rails user handle this?
I don't think using the database manager or looking at loads of migrations scripts to find the attributes is an option?
Specifying attr_accessible will make the model more readable but I'm not sure if it's a proper solution for my problem?
Check out the annotate_models plugin on github. It will insert a commented schema for each model in a comment block. It can be installed to run when migrate is.
You don't have to "look at loads of migration scripts to find the attributes" - they're all defined in one place in db/schema.rb.
A few tips:
Load up the Rails console and enter
Post.column_names for a quick
reminder of the attribute names.
Post.columns gives you the column
objects, which shows the datatypes
db/schema.rb contains all the
migration code in one place, so you
can easily see all the column
definitions.
If you are using a
decent editor/IDE there should be a way to
allowing you to jump from the model file
to the migration file. (e.g. Emacs
with ROR or Rinari)

rails has_and_belongs_to_many or manual implementation

I'm designing a ruby on rails app for a pharmacy, and one of the features is that there are stores who have pharmacists who work there. In addition, there are pharmacists, who can work at many stores. This sounds like a job for HABTM, right? Well, being the novice I am, I manually designed a workaround (because I never heard of HABTM - I basically taught myself rails and never got to some of the more advanced relationships). Right now, when a pharmacist is saved, there's a couple of lines in the create and update action of the pharmacists controller that turns the stores that they work at into a string, with each store_id separated by a comma. Then, when a store is displayed, it does a MYSQL request by
#pharmacists = Pharmacist.find :all, :conditions => "stores REGEXP '#{#store.id}'"
Would moving this system over to a rails based HABTM system be more efficient? Of course it would require less code in the end, but would it be worth it? In other words, what benefits, other than less code, would I get from moving this association to be managed by rails?
The benefit is that you will be using the right tool for the job! The whole point of using a framework such as Rails is that it helps you solve common problems without having to re-invent the wheel, which is what you've done here. By using associations you'll also be using a relational database properly and can take advantage of benefits like foreign key indexing, which will be faster than string manipulation.
You should use a has_and_belongs_to_many relationship unless you need to store extra attributes on the join model (for example the date a pharmacist started working at a store) in which case use has_many :through.
Using Rails associations will give you all the convenient methods that Rails provides, such as these:
# Find the stores the first pharmacist works at
#stores = Pharmacist.first.stores
# Find the pharmacists who work at a store
#pharmacists = Store.find_by_name('A Store').pharmacists
A Guide to ActiveRecord Associations

Resources