Rails 3 joins table and model naming conventions - ruby-on-rails

I am running Ruby 2.1.9 and Rails 3.2.17.
First off, in Rails I always made the assumption that models should almost always be singular. I have seen model names like product_categories_product. This is fine and in habtm situation where you want a model to work with the relationship I have seen instructions_products. Which in Rails sorta may make sense, but I would rather name the model instruction_product. That was associated with a joins table named instructions_products.
In fact I was so frustrated I started looking at how rails names things and this happened? So clearly its an ok name for the model and would correspond to the correct table name. But what is more approriate?
ActiveModel::Naming.singular(InstructionsProducts)
returns instructions_products
Edited: The heart of the question is why is InstructionsProducts a valid model name in rails and why does my example above resolve to the singular 'instructions_products'. This seems odd considering the rails convention is to keep model names singular.

Your question is not completely clear to me.
By Rails conventions, model names are singular and written in camel case, for instance InstructionProduct. Each model matches a table in the database with the same words, down-cased, separated by '_' and in plural. instruction_products for the provided example.
Look at the following example using has_many:
class User < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belong_to :name
end
user = User.find(1)
user.contacts # returns an array of all the associated objects
When doing user.contacts, contacts is not the table name. It's the collection method, a placeholder for the symbol passed in the has_many method (please follow the has_many link and read what documentation says about has_many). Another name could be used, though:
class User < ActiveRecord::Base
has_many :personal_contacts, class_name: 'Contact' #, foreign_key: :contact_id
end
user = User.find(1)
user.personal_contacts
The class_name and foreign_key are required because rails conventions are not being followed. When using has_many :personal_contacts rails expects that personal_contacts will return an array of PersonalContact.
In Ruby you must start a class name with a capital word, so it is not possible to create a class named instruction_product. If you want to provide a name that does not follow the Rails convention, which I don't recommend, you will need to inform rails about the new table name:
Class AdminUser
self.table_name = "users"
end
Update 1:
As you already know, the convention states that the model should be declared as singular (class InstructionProduct instead class InstructionsProducts. However its just a convention. When a class inherits from ActiveRecord::Base, and a sql query is generated, ActiveRecord lowercases the class name, separates the words by _, converts to a plural name and uses it as the table name (mainly rails uses InstructionsProducts.model_name.plural which returns instructions_products).
You are assuming that singular actually does a name translation to singular, even if it's written in plural, but it doesn't. It assumes that you are using the convention, and mainly returns the class name underscored.
Looking at the rails source code (ActiveModel::Name), ActiveSupport::Inflector.underscore seems to be used (I just did a very superficial investigation, I have to admit). You can see how underscore works at documentation.

Related

How to access has_many through relationship possible in rails?

How can I access my related records?
class Post < ActiveRecord::Base
has_many :post_categories
has_many :categories, through: :post_categories
class Categories < ActiveRecord::Base
has_many :post_categories
has_many :post, through: :post_categories
class PostCategories < ActiveRecord::Base
belongs_to :post
belongs_to :category
PostCategories table has id, posts_id, and categories_id columns.
id | posts_id | categories_id
1. | 2 | 3
2. | 2 | 4
What I want is: to get posts related to a category. like: all Posts where in x category.
Yep, this is an easy one.
one_or_more_categories = # Category.find... or Category.where...
posts = Post.joins(:categories).where(category: one_or_more_categories)
Rails is clever enough to take either a model or a query that would find some data and turn that into an efficient appropriate query, that might be a subquery. Trying things out in the Rails console (bundle exec rails c) is a good way to see the generated SQL and better understand what's going on.
(EDIT: As another answer points out, if you've already retrieved a specific Category instance then you can just reference category.posts and work with that relationship directly, including chaining in .order, .limit and so-on).
Another way to write it 'lower level' would be:
Post.joins(:categories).where(category: {id: one_or_more_category_ids})
...which is in essence what Rails will be doing under the hood when given an ActiveRecord model instance or an ActiveRecord::Relation. If you already knew the e.g. category "name", or some other indexed text column that you could search on, then you'd adjust the above accordingly:
Post.joins(:categories).where(category: {name: name_of_category})
The pattern of joins and where taking a Hash where the join table name is used as a key with values nested under there can be taken as deep as you like (e.g. if categories had-many subcategories) and you can find more about that in Rails Guides or appropriate web searches. The only gotcha is the tortuous singular/plural stuff, which Rails uses to try and make things more "English-y" but sometimes - as in this case - just creates an additional cognitive burden of needing to remember which parts should be singular and which plural.
Not sure if this answers it but in ActiveRecord your Post will have direct access to your Category model and vice versa. So you could identify the category you want the posts from in a variable or an instance variable, and query #specific_category.posts. If you are doing this in your controller, you could even do it in before_action filter. If you are using it in serializers its not much different.
You could also create a scope in your Post model and use either active record or raw SQL to query specific parameters.
You also have an error in your Category model. Has many is always plural so it would be has_many :posts, through: :post_categories
Get the category object and you can directly fetch the related posts. Please see the following
category = Category.find(id)
posts = category.posts
Since you have already configured the has_many_through relation, rails will fetch post records related the category.

rails inverse of using 'references' in generators

When using model and migration generators in rails, you can use 'references' keyword as it was a data type to indicate that that field is a foreign key to another model, that is another table.
In this example I do so with an author field inside a book table (actually the field won't be exactly 'author').
rails g model book title publication_date:date author:references
Looking at the migrations and models created, we can better understand what are the information that rails has guessed from the command (ok this is not a part of the question but more a summary of the logic and the state of the art of this command):
Migration
class CreateBooks < ActiveRecord::Migration
def change
create_table :books do |t|
t.string :title
t.references :author, index: true
t.date :publication_date
t.timestamps
end
end
end
Here rails uses again the 'references' method as it was a data type, but it actually means that we are a layer on top on the 'basic' data structure level, infact referencing an author means, at the data level, to add an 'author_id' and not an 'author' column . And it does right so (i've also checked on schema.rb to have confirmation).
In addition to this, it also puts an index: true that is a good practice from a performances point of view.
Model
Calling the generator in such a way also 'does something' on the model:
class Book < ActiveRecord::Base
belongs_to :author
end
that is, it adds the 'belongs_to :author'.
Notice that you can even create the book model and migration with the references clause to author before having author created. It enforces the consistence at the end (if it does at all, on such things).
Question
My question is on what rails doesn't do, that is: the has_many clause on the author model.
1) since normally it does exist before having created the book model, it should be easy for rails to add has_many to this. Is there any parameter to say rails to do so?
2) because of the fact that models can reference other models even if they are not yet created, another possibility would be declaring some kind of 'has_many' reference during author creation, is it possibile in some way?
There's no way of knowing where to put it, you'd have to specify it.
Such generators are designed to save time, and making such a thing saves too little or none. For instance, you'll have to specify explicitly the target class and whether it is has_one or has_many. That would subtract some of the usefulness of this, resulting in a very small positive or even a negative value.
I assume you mean that associations are available even on unsaved models. Yep, they are, but they're not too reliable. It works quite simple: an unsaved object has a collection for each association which stores temporary objects. When the main object is saved, new associated objects in these collections (not persisted yet) are created. An example:
a = Author.new(name: "Charles Dickens")
a.books << Book.new(title: "Oliver Twist")
a.save
That would save the new author to the database, and since his id would be known, it would then create a new book with that author_id. This behaves a little odd when it comes to has_many ... through: ... and possibly some other cases, so use this carefully.

Query records through its belongs_to relation in Rails 4

I'm hoping for a little clarification on the correct way of querying records through its belongs_to relation in Rails 4.
I would like to find all parts that have a specific category name.
I can make it work with category ID using: Part.where(category_id: "1")
But I can't get it to take with the name.
If I do this:
Part.joins(:categories).where("categories.name = 'cars'").first
I get back this error:
ActiveRecord::ConfigurationError: Association named 'categories' was not found on Part; perhaps you misspelled it?
I don't see where I misspelled anything and I can't figure out why it's not working. Any help would be greatly appreciated!
Category Model:
class Category < ActiveRecord::Base
has_many :parts
end
Parts Model:
class Part < ActiveRecord::Base
belongs_to :category
end
Category Table:
CREATE TABLE categories
(
id serial NOT NULL,
name character varying(255),
CONSTRAINT categories_pkey PRIMARY KEY (id)
)
Parts Table:
CREATE TABLE parts
(
id serial NOT NULL,
category_id integer,
CONSTRAINT parts_pkey PRIMARY KEY (id)
)
Your belongs_to association name is singular (which is correct, as it the standard) in the Part model, but you tried to join using the pluralized association name (you tried :categories). A common mistake; you probably got mixed up between the database naming convention, and the rails association naming convention. Try instead this:
Part.joins(:category).where('categories.name = "cars"').first
Side note:
This is not related to the question, but in case you did not already know, you can use a hash in the where clause if you like as well. It would look like:
where(categories: {name: "cars"})
And the result would be the same. In your case, it is not relevant since you're clearly passing a literal string, but it may come in handy when you're using form data, or something equally non-secure, since the hash method will properly treat the parameter as text, rather than dangerously placing it in the query as-is.
Use: Part.joins(:category)
The association name you pass to joins should mimic your declaration of it: belongs_to :category. Here you use :category, so give joins the same symbol.

Rails: Creating models from existing tables?

I have tables already created from a different project. Their names are formatted like aaa_bbb_ccc_ddd (all non plural and some parts aren't a convention word). I have successfully created a schema from the database by reading this. But now I have to make the actual models. I've looked at RMRE, but they enforce the ActiveRecord convention on my tables and change their names, which I don't want to do because other apps depend on those tables.
What is the best way to automatically create models and a schema from existing tables?
just a theory, not sure how this would work in real app:
create models named as ActiveRecord convention requires, for example for table aaa_bbb_ccc_ddd you'll create a model AaaBbb and map this model to your table:
class AaaBbb < ActiveRecord::Base
self.table_name = "aaa_bbb_ccc_ddd"
end
or a more human example:
class AdminUser < ActiveRecord::Base
self.table_name = "my_wonderfull_admin_users"
end
Now you'll have AaaBbb as resource in routes meaning you'll have a url like:
.../aaa_bbb/...
and if you want to use the table name name in url I guess you could rewrite the route:
get 'aaa_bbb_ccc_ddd/:id', "aaa_bbb#show", as: "aaa_bbb"
again, just a theory that might help you out. I haven't worked with such cases yet but would've start from this.
edit
to automate model creation from database:
https://github.com/bosko/rmre
but I think this will create models by rails convention with wierd names that you'll have to use as resource in your app.
A good template that I found on SO in case you want to use a model name different from table name:
class YourIdealModelName < ActiveRecord::Base
self.table_name = 'actual_table_name'
self.primary_key = 'ID'
belongs_to :other_ideal_model,
:foreign_key => 'foreign_key_on_other_table'
has_many :some_other_ideal_models,
:foreign_key => 'foreign_key_on_this_table',
:primary_key => 'primary_key_on_other_table'
end
Just switch from Rails to Django and make
your life happier and also make your work normal:
$ python manage.py inspectdb my_table_without_existing_model > some_new_model.py
That's enough. Two seconds of work :)

Is it possible to have a dynamic id column in Rails 3 when using single table inheritance

I have a Change model that utilizes single table inheritance that has the following attributes:
id
type #this is a single table inheritance type field.
description
dynamic_id
I also have two sub classes, Race which is a subclasses of Change and Workout which is a sub class of Race.
class Race < Change
end
class Workout < Race
end
I have a fourth class called Track and I'd like to create the following four associations by just using the dynamic_id field in the Change object. (i.e. I have not explicitly added race_id and workout_id to the Change table. Instead I want to use the dynamic_id as the race_id for the Race class and the dynamic_id as the workout_id for the Workout class) By doing this, I will avoid having a lot of nil fields in my database.)
Here are the four associations I'm trying to create.
Race Model - belongs_to :track
Workout Model - belongs_to :track
Track Model - has_many :races
Track Model - has_many :workouts
I've been trying to accomplish this with associations using :class_name and :foreign_key, but I can't seem to get it working. Is this actually possible. I realize its probably not a best practice, but I'd still like to see if it doable. Thanks for your input.
What you are looking for are "polymorphic associations". You can find more in the rails guides: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Your case is a bit special because you want to use polymorphic associations with STI. I remember that there was a bug with this combination but it could be fixed by now.
I did not read it completely but this blog post seems to describe the situation: http://www.archonsystems.com/devblog/2011/12/20/rails-single-table-inheritance-with-polymorphic-association/
The problem I encountered with polymorphic associations and STI is described here: Why polymorphic association doesn't work for STI if type column of the polymorphic association doesn't point to the base model of STI?

Resources