Rails routing for link tables without primary key? - ruby-on-rails

I have a link table which looks like this:
create_table "links", :id => false, :force => true do |t|
t.integer model1_id
t.integer model2_id
t.string someotherinfo
end
I'm currently defining routes like this:
match '/links/:model1_id/:model2_id/' => buggable_links#validate
It seems like I ought to be able to do something more like resources rather than writing out all the match statements. What's the right way of having rails generate resource routes on models which do not have a single primary key, such that URLs contain two IDs?
N.B. I'm aware that one possible answer is 'just add an autoincrementing pk'. The pros and cons of that are discussed in this question, but for the purposes of this question let's assume I want to leave my DB schema as it is.

This is the correct way to do it.
The only thing you should add is in the resources of buggable_links add the validate function as a get method.

Related

Separating multiple models in Rails form submission

Specifically, I have a form for creating a User. One of the fields is Group, which is a separate model. During the User#create action I call Group.find_or_create_by_name to check and see if the Group already exists by pulling out params[:user][:group], and create the Group if it doesn't exist.
But, when I create the User, I can't pass params[:user], because params[:user][:group] is not a group, it's a String. This would be a lot easier if I could supply params[:user] and params[:group] to my controller, instead of everything bundled under a single variable, but I don't know how to do that.
Relevant code:
User#create
#group = Group.find_or_create_by_name(params[:user][:group])
#group.save!
#user = #group.users.build(params[:user])
Partial User Schema
create_table "users", :force => true do |t|
t.string "name", :default => "", :null => false
t.string "email", :null => false
t.integer "group_id"
Partial Group Schema
create_table "groups", :force => true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
Params Dump from form submission
{"commit"=>"Register",
"authenticity_token"=>"x1KgPdpJop5H2NldsPtk0+mBDtrmpM/oNABOxjpabIU=",
"utf8"=>"✓",
"user"=>{"name"=>"aName",
"group"=>"aGroupName",
"password_confirmation"=>"[FILTERED]",
"password"=>"[FILTERED]",
"email"=>"anEmail"}}
The best explanation I've run across is this question.
I don't really want to start rearranging the params has inside of my controller as that really makes me queasy, and seems like terrible programming. Which really makes me question whether I've got a bigger problem.
If it goes against Rails conventions to do this, what then is the best way to do it, and why should I not?
I don't think the proper way to do this here is to build the user off the group. That'd be appropriate if you were in an action of the GroupsController that, for example, added new users to a group. I think the best approach here would be to do the following:
#group = Group.find_or_create_by_name(params[:user][:group])
#group.save!
#user = User.new(params[:user])
#user.group_id = #group.id
#user.save
Since you're in the new action of the UsersController, it seems more fitting to be creating a new user instead of building it off an association and then adding that user to the group that either already existed or was just created.
Does that make sense?
So, I'm going to spell out everything I've learned, in case other people are having trouble understanding this like I did. Also, if I get anything wrong here, please correct me.
If you're using a form that creates multiple model instances at once (preferably associated ones), you first need to use the helper accepts_nested_attributes_for in your model definition (probably right underneath your declared associations). The reason for this is it creates a setter method that knows how to write that type of associated model. (Note: you can also define this method yourself in your main model). Once you've done that you can nest a fields_for inside of a form_for, and Rails will know how to make the proper assignments.
I initially thought that accepts_nested_attributes_for was referring to nested resources, which is definitely not the case. If you're looking for more information, refer to section 11.8.3 (pp 343-347) of The Rails 3 Way.

Ruby on rails, `rails generate` with primary_key not the default integer id

I have a model object called 'Problem' and I want to be able to call its the 'show' action using the URL /problems/PROBLEM_NAME.
I think the way to go about this is to change it so the primary_key is no longer the default integer id, instead I want it to be a string of letters.
I have tried to rails g model problem problem_name:primary_key problem_text:text
but I get the obvious error about having multiple primary keys.
Any ideas?
I guess in your routes you have:
resources :problems
Before the line with resources :problems, in your routes.rb file add this
match "problems/:problem_name" => "problems#show"
After you added the new show route, in your problems controller, in the show action, instead of doing
#problem = Problem.find(params[:id])
you use now
#problem = Problem.find_by_name(params[:problem_name])
Doing it this way, you don't have to modify your database
In generated migration, change this row:
create_table :problems do |t|
to this:
create_table :problems, :id => false do |t|
But I would recommend you using a slug as an extra column instead of text primary key...

Adding Related Model Object to JSON Encoding, When Foreign Key is Unconventional

I'm trying to encode an array of model objects to JSON, including data related through association. But the foreign key field in my schema is not modelname_id, so I can't simply do ActiveSupport::JSON.encode(xxx,:include=>{:modelname}). The reason for this schema difference is something I feel should be common. So there might be an established best practice, but I don't know that.
relevant migration file:
def self.up
create_table :tables do |t|
t.integer :room_id
t.integer :table_num
t.integer :user_1_id
t.integer :user_2_id
t.timestamps
end
end
Since each table can have two users sitting down, I can't simply use user_id, but had to use user_1_id and user_2_id
Now, suppose I have an array of tables I want to encode to json, along with it I want each table object to include user_1_email:'xxx' and user_2_email:'xxx' fields. How can I do that?
Also, when I search for ActiveSupport::JSON.encode in various online documentation, I cannot find anything written on what I can do with options.
For instance, the api in
http://api.rubyonrails.org/
only tells me the default option parameter is nil, but doesn't say anything about what options I can use. Am I missing something?
On a side note, can I also add non-model attributes in JSON encoding?
I've been searching for a way for a few days but can't find anything.
You can easily customize and extend the JSON representation of an object by implementing a custom #as_json method.
Example:
class Table
def as_json(options = nil) # This is just for interface compatibility
{
:id => #id,
:user_1_email => #user1.email,
:user_2_email => #user2.email,
:something_else => generate_something
}
end
end

Scaffolding in Rails while defining nullable fields and foreign keys

I'm just figuring out my way around rails but I need a little help with the rails generate scaffold command.
Here's the command that I'd like to use
rails generate scaffold Expense user:??? name:string description:text
I'd like the description field to be nullable and the users field to be linked to another Model — in this case I'd like to create a foreign key to the Users. I'm using the devise authentication framework.
I've read that many RoR developers try and avoid the scaffolding method and opt for the manual approach instead but my web-app is quite simple and I've thought of going the scaffolding way.
Scaffolding only generates the migration that you then run. Once the file is generated simply crack open the generated migration and adjust any of the values you need specific constraints on. By default columns are set to null unless you specify otherwise e.g.:
create_table "slugs", :force => true do |t|
t.integer "sequence", :default => 1, :null => false
t.string "sluggable_type", :limit => 40
t.string "scope", :limit => 40
t.datetime "created_at"
end
This is the code generated by the friendly_id plugin as you can see they have specified that the sequence column cannot be null while the other fields have other constraints.

What indexes should be added for a polymorphic association in Ruby on Rails?

I have a polymorphic association in a Ruby on Rails model. In the migration, I have:
create_table "offer_defs" do |t|
t.integer "product_def_id"
t.string "product_def_type"
...
end
I would like to add an index for this association. I am hesitating between the following options:
add_index :offer_defs, [:product_def_id, :product_def_type]
or
add_index :offer_defs, :product_def_id
add_index :offer_defs, :product_def_type
or maybe both ?
What are the pros and cons?
I would go with the latter option. It'll help if you need to count polymorphic attachments of a specific type, which you might. If you wanted the multi column index, I'd at least swap the order of the two columns (putting type first), since that type field is much more likely to be a query param on this table than the id field.
note: I'm assuming you're using mysql here, and my advice should probably be ignored, or at least checked, if you're using a different db.

Resources