I want users to be able to select the languages they speak. I have setup the associations, the table attributes and the part of the form. When I select a language and submit the form I go to the rails console and do a u.languages but I get an empty array back: => []
Here are the logs when I submit the form:
Started POST "/update_user_via_user" for 127.0.0.1 at 2016-03-18 13:26:03 +0200
ActiveRecord::SchemaMigration Load (0.4ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by UsersController#update_user_via_user as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"CB1Qca0VrBcap9qO6VpKfoi2dG8GNG+tGGNDgCnFEv4E=", "user"=>{ "fullname"=>"John Doe", "languages"=>["", "1", "2"]}, "commit"=>"Save"}
User Load (28.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 ORDER BY "users"."id" ASC LIMIT 1
Unpermitted parameters: languages
(0.1ms) begin transaction
(0.1ms) commit transaction
Redirected to http://127.0.0.1:3000/setup_profile
Completed 302 Found in 163ms (ActiveRecord: 29.5ms)
Now if you look closely on the loogs you will see "Unpermitted parameters: languages".
In my users_controller I have the followings:
def user_params
params.require(:user).permit(:languages, :fullname)
end
and the custom action:
def update_user_via_user
if current_user.update(user_params)
flash.notice = "Your profile was sent for moderation. We will moderate it asap!"
else
flash.alert = "Something went wrong! Please try again."
end
redirect_to root_path
end
Some other references: (my question at the end)
schema.rb:
languages table:
create_table "languages", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
end
users table:
t.string "languages"
language.rb model:
class Language < ActiveRecord::Base
belongs_to :user
end
and user.rb model:
class User < ActiveRecord::Base
has_many :languages
end
The view:
<%= f.label :languages %>
<%= f.select :languages, Language.all.map{ |l| [l.name, "#{l.id}"] }, {}, { :multiple => true } %>
I am not sure why "languages" is not permitted and also if my concept of code is correct
class Language < ActiveRecord::Base
belongs_to :user
end
This would setup a one two many relation between users and languages which is not what you want. What you want is a many to many relation:
Users can speak many languages
Languages have many speakers (users)
So to keep track of this we need a third table:
language_users is what is known as a join table. You can actually name the table whatever you want - calling it a + _ + bs is just a convention.
We also need to setup our models to use the join table.
class User < ActiveRecord::Base
has_many :language_users
has_many :languages, through: :language_users
end
class Language < ActiveRecord::Base
has_many :language_users
has_many :users, through: :language_users
end
# this is a join model that links User & Language
class LanguageUser < ActiveRecord::Base
belongs_to :user
belongs_to :language
end
To create a form element where users can select languages you would use:
<%= f.collection_select(:languages_ids, Language.all, :id, :name, multiple: true) %>
Or:
<%= f.collection_check_boxes(:languages_ids, Language.all, :id, :name) %>
languages_ids is a special accessor that ActiveRecord creates for has_many associations that lets you set multiple associations at one by passing an array of ids.
I was thinking Deep's answer was the right one, based on this exchange.
I fired up my console and did this:
irb(main):001:0> x = ActionController::Parameters.new(user: {fullname: "John Doe", languages: ['','1','2']})
=> {"user"=>{"fullname"=>"John Doe", "languages"=>["", "1", "2"]}}
irb(main):002:0> y = x.require(:user).permit(:fullname, :languages => [])
=> {"fullname"=>"John Doe", "languages"=>["", "1", "2"]}
irb(main):003:0> y[:languages]
=> ["", "1", "2"]
So, hm.
What's the error message you're getting now? Same as original?
Related
In an effort to create a Short, Self Contained, Correct (Compilable), Example, imagine that I want to do the following.
I have a blog website. There are two types of posts, TextPost and LinkPost. There are also two types of users, User and Guest. I would like to implement Multiple Table Inheritance with TextPost and LinkPost, by which I mean (hopefully I'm using the term correctly):
At the model level, I will have Post, TextPost and LinkPost. TextPost and LinkPost will inherit from Post.
At the database level, I will have tables for the "leaf" models of TextPost and LinkPost, but not for Post.
Each type of Post can belong to either a User or a Guest. So we have a polymorphic belongs_to situation.
My question is how to accomplish these goals.
I tried the following, but it doesn't work.
class Post < ApplicationRecord
self.abstract_class = true
belongs_to :author, polymorphic: true # user or guest
validates :title, :author_id, :author_type, presence: true
end
class TextPost < Post
validates :content, presence: :true
end
class LinkPost < Post
validates :url, presence: :true
end
class User < ApplicationRecord
has_many :text_posts, as: :author
has_many :link_posts, as: :author
validates :name, presence: true
end
class Guest < ApplicationRecord
has_many :text_posts, as: :author
has_many :link_posts, as: :author
end
class CreateTextPosts < ActiveRecord::Migration[6.1]
def change
create_table :text_posts do |t|
t.string :title
t.string :content
t.references :author, polymorphic: true
t.timestamps
end
end
end
class CreateLinkPosts < ActiveRecord::Migration[6.1]
def change
create_table :link_posts do |t|
t.string :title
t.string :url
t.references :author, polymorphic: true
t.timestamps
end
end
end
class CreateUsers < ActiveRecord::Migration[6.1]
def change
create_table :users do |t|
t.string :name
t.timestamps
end
end
end
class CreateGuests < ActiveRecord::Migration[6.1]
def change
create_table :guests do |t|
t.timestamps
end
end
end
Console output:
:001 > user = User.create(name: 'alice')
(1.6ms) SELECT sqlite_version(*)
TRANSACTION (0.1ms) begin transaction
TRANSACTION (0.1ms) SAVEPOINT active_record_1
User Create (1.2ms) INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "alice"], ["created_at", "2021-06-11 23:33:38.445387"], ["updated_at", "2021-06-11 23:33:38.445387"]]
TRANSACTION (0.2ms) RELEASE SAVEPOINT active_record_1
:002'> text_post = TextPost.create(title: 'foo', content: 'lorem ipsum', author_id: 1, author_type:
'user')
Traceback (most recent call last):
1: from (irb):2:in `<main>'
NameError (wrong constant name user)
The names of constants look like the names of local variables, except that they begin with a capital letter.
All the built-in classes, along with the classes you define, have a corresponding global constant with the same name as the class called class name.
So in your case, when you define User class, there's a constant class name: User, but not user, that why the error NameError (wrong constant name user) is raised.
try text_post = TextPost.create(title: 'foo', content: 'lorem ipsum', author_id: 1, author_type: 'User')
Okay, setting aside the fact that asking how to "destroy orphans" on a chat board is just evil, here is my technical question:
I am making a simple blog-site, using Activerecord and Sinatra (and I am new to both). I have a "many to many" relationship between a Post model and a Tag model, and have been getting errors that I was not able to sort out. Finally, I arranged the models and migration so that I am not getting errors; however, when I destroy an instance of a tag, the the associations on the "posts_tags" table are left in place (existing as orphans), and I want to know how to destroy them (cue evil music).
Can anyone assist me with this?
Here are my migration for all three tables:
class CreatePostsTable < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t|
t.string :title, default: 'title here'
t.string :content, default: 'content here'
t.belongs_to :blog, index: true
t.timestamps
end
end
end
class CreateTagTable < ActiveRecord::Migration[5.2]
def change
create_table :tags do |t|
t.string :name, unique: true
end
end
end
class CreatePostsTagsTable < ActiveRecord::Migration[5.2]
def change
create_table :posts_tags, :id => false do |t|
t.belongs_to :tag, index: true
t.belongs_to :post, index: true
# t.integer :post_id
# t.integer :tag_id
end
end
end
And here are my three models:
file-name: Post_Tag (I have named, and re-named this class and file-name over the course of a couple days).
class PostsTag < ActiveRecord::Base
# self.table_name = "posts_tags" #had to add this line for seed file not to give errors
belongs_to :post
belongs_to :tag
end
class Post < ActiveRecord::Base
belongs_to :blog
has_many :posts_tags
has_many :tags, :through => :posts_tags
has_many :comments, dependent: :destroy
end
class Tag < ActiveRecord::Base
has_many :posts_tags
has_many :posts, :through => :posts_tags
# has_and_belongs_to_many :posts, :through => :posts_tags
end
This configuration is not giving me error when I search for a post's tags, or a tag's posts, BUT when I do #tag = Tag.find_by_id(1) #tag.destroy the "posts_tags" table is left still having all of the tag_id == 1 rows still there. When I try to destroy them, I get errors (which I think is because that would also destroy the associations).
Does anyone know how to fix this? Is it okay to simply remove the PostsTag model all together?
The Activerecord documentation uses an example where the join-table is a single word, so I wasn't able to find the answer there.
I saw that some people were simply not making a model for the join-table. When I removed my join-table model, I got this error:
#post = Post.find_by_id(1)
#post.tags.each do |tag|
p tag.name
end
NameError at /
uninitialized constant Tag::PostsTag
Thanks for any help!
#
Phase Two of question:
After the tip that I could try adding this code to my models:
class Post < ActiveRecord::Base
has_many :posts_tags, dependent: :destroy
class Tag < ActiveRecord::Base
I am running this in my server/app file:
delete "/tag/destroy" do
body = JSON.parse request.body.read # body: {id: number} or {name: string}
#tag_to_destroy = Tag.where(body)[0]
#tag_to_destroy.destroy
redirect "/tag"
end
I am sending this request via postman:
http://localhost:4567/tag/destroy
body of request:
{
"id": 12
}
And I am getting this error:
(even though there is are rows in the posts_tags database with tag_id = 12, post_id = variousNumbers):
== Sinatra (v2.0.1) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
* Version 3.11.4 (ruby 2.5.1-p57), codename: Love Song
* Min threads: 0, max threads: 16
* Environment: development* Listening on tcp://localhost:4567
Use Ctrl-C to stop
D, [2018-05-07T10:54:19.604906 #99099] DEBUG -- : Tag Load (0.5ms) SELECT "tags".* FROM "tags" WHERE "t
ags"."id" = $1 [["id", 12]]
D, [2018-05-07T10:54:19.617955 #99099] DEBUG -- : (0.3ms) BEGIN
D, [2018-05-07T10:54:19.633736 #99099] DEBUG -- : PostsTag Load (1.5ms) SELECT "posts_tags".* FROM "pos
ts_tags" WHERE "posts_tags"."tag_id" = $1 [["tag_id", 12]]
D, [2018-05-07T10:54:19.642305 #99099] DEBUG -- : PostsTag Destroy (1.6ms) DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL
D, [2018-05-07T10:54:19.642685 #99099] DEBUG -- : (0.2ms) ROLLBACK
2018-05-07 10:54:19 - ActiveRecord::StatementInvalid - PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
LINE 1: DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL
^
: DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL:
/Users/maiya/.rvm/gems/ruby-2.5.1/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:603:in `async_exec'
Question update:
The join-table migration was previously:
create_table :posts_tags :id => false do |t|
t.belongs_to :tag, index: true
# comment: the above line creates: t.integer :post_id
t.belongs_to :post, index: true
# comment: the above line creates: t.integer :tag_id
end
All is OK now with your migrations and models, you just need to change in the Post and Tag models:
has_many :posts_tags
to
has_many :posts_tags, dependent: :destroy
In this case after you destroy any tag or post all associated posts_tags are destroyed too, it will ensure referential integrity of your app and prevent errors.
Also, it is good idea to rollback migrations, add foreign_key: true option to t.belongs_to lines, and migrate it again. This option will provide referential integrity on db level.
UPDATE:
has_and_belongs_to_many (HABTM) doesn't work with through option, you mixes it up with has_many association. Please, read about it more in guides
To make it all working you need to:
remove :id => false option from CreatePostsTagsTable migration
remigrate or drop and recreate db
change has_and_belongs_to_many <...>, :through => :posts_tags to has_many <...>, :through => :posts_tags in both Tag and Post models
P.S. By convention all file names in ruby are in lower snake case, it should be `posts_tag.rb', not 'Posts_Tag.rb'
I'm working on a app where users can find recipes made by chefs. I have implemented already a "like/dislike" fonction but now I would like to give to the user the ability to save a recipe in a collection.
The user can create many collections, so when he want to save a recipe in a collection, he should be able to see his current collections or create a new one.
Here are my models:
class User < ActiveRecord::Base
has_many :collections
accepts_nested_attributes_for :collections
end
class Collection < ActiveRecord::Base
has_many :recipe_collections
has_many :recipes, through: :recipe_collections
end
class Recipe < ActiveRecord::Base
has_many :recipe_collections
end
class RecipeCollection < ActiveRecord::Base
belongs_to :recipe
belongs_to :collection
end
my recipe_collections_controller.rb
class RecipeCollectionsController < ApplicationController
before_action :set_recipe, only: [:create]
before_action :set_user, only: [:create]
def create
#recipe_collection = #recipe.recipe_collections.build(recipe_collection_params)
if current_user
#recipe_collection.save
respond_to do |format|
format.html {redirect_to :back }
end
else
respond_to do |format|
format.html { render 'recipes/show' }
end
end
end
private
def set_user
#user = current_user
end
def set_recipe
#recipe = Recipe.find(params[:recipe_id])
end
def recipe_collection_params
params.require(:recipe_collection).permit(:collection_id, :recipe_id, collection_attributes: [:id], recipe_attributes: [:id])
end
end
In the recipe show, I have a render, show.html.erb:
<%= render "recipe_collections/form", collection: #recipe_collection || #recipe.recipe_collections.build%>
my partial _form.html.erb
<%= simple_form_for ([ collection.recipe, collection ]) do |form| %>
<%= form.association :collection, as: :check_boxes %>
<%= form.button :submit %>
<% end %>
my routes.rb
resources :recipes, :concerns => :paginatable do
member do
get "like", to: "recipes#upvote"
get "dislike", to: "recipes#downvote"
end
resources :reviews, only: :create
resources :recipe_collections
end
What is the problem:
1: in my show I have a checkbox form, but it don't display the current user collections but all.
2: when I submit the form, it don't save the recipe in the collection that I chose.
edit:
here are an exemple of my logs when I submit the form.
Started POST "/recipes/66/recipe_collections" for ::1 at 2015-04-06 23:58:06 +0200
Processing by RecipeCollectionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"GhMIJs4nNmwsLpzBj4l5ta/OW6fN9dfuzBBnciJsjEa0YlfjQMQYDEmwhcXb9++oEmS36yEbgICNsSdbLgiigA==", "recipe_collection"=>{"collection_id"=>["24", ""]}, "commit"=>"Créer un(e) Recipe collection", "recipe_id"=>"66"}
(0.4ms) SELECT COUNT(*) FROM "recipes"
(0.2ms) SELECT COUNT(*) FROM "publishers"
(0.2ms) SELECT COUNT(*) FROM "votes"
Recipe Load (0.2ms) SELECT "recipes".* FROM "recipes" WHERE "recipes"."id" = $1 LIMIT 1 [["id", 66]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["id", 5]]
Unpermitted parameter: collection_id
(0.1ms) BEGIN
SQL (0.3ms) INSERT INTO "recipe_collections" ("recipe_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["recipe_id", 66], ["created_at", "2015-04-06 21:58:06.566948"], ["updated_at", "2015-04-06 21:58:06.566948"]]
(0.4ms) COMMIT
Redirected to http://localhost:3000/recipes/66
Completed 302 Found in 11ms (ActiveRecord: 2.0ms)
Its a bit confusing whether you are setting up a relationship between recipes and collections as many-to-many or one-to-many. Your text implies a collection has many recipes and a recipe belongs to one collection. While your code, by using a join table and stating a recipe has many recipe-collections implies you are setting up a many-to-many relationship.
In other words you are a bit mixed up...
If a recipe belongs to a collection. Get rid of the join table. Recipe belongs_to :collection, while Collection has_many :recipes. Get rid of the other relationship stuff in those two models. In your migration table for Recipe, make sure there is a t.belongs_to :collection to set up the foreign key.
If a recipe has and belongs to many collections. Keep the join table, but name it collections_recipes as Rails can only find it if both sides are pluralized and in alphabetical order. Recipe has_and_belongs_to_many :collections, Collection has_and_belong_to_many :recipes. Drop the has_many :recipe_collections and the through: bit, do not replace either, Rails does this for you. Check your join table in your migration has t.belongs_to :recipe and :collection
Hope this helps.
I am having trouble figuring out how to make a nested form using a has_many :through relationship. I used this Railscast and I took at look at this tutorial and lots of the questions on Stack Overflow and elsewhere around Google.
I'm trying to make a way to create tags via the articles form. My code has gone through lots of iterations based on information from lots of different sources and none of them have worked, but right now I have
A class for articles
class Article < ActiveRecord::Base
attr_accessible :content, :heading, :image, :tag_ids, :tags, :tag_name, :tag_attributes
belongs_to :user
has_many :comments, :dependent => :destroy
has_many :article_tags
has_many :tags, :through => :article_tags
accepts_nested_attributes_for :tags, :reject_if => proc { |attributes| attributes['tag_name'].blank? }
...
end
A class for tags
class Tag < ActiveRecord::Base
attr_accessible :tag_name
has_many :article_tags
has_many :articles, :through => :article_tags
end
A class for article_tags
class ArticleTag < ActiveRecord::Base
belongs_to :article
belongs_to :tag
end
The New in my articles_controller.rb is like this:
def new
#article = Article.new
#tags = Tag.find(:all)
article_tag = #article.article_tags.build()
#article_tags = #article.tags.all
#article.article_tags.build.build_tag
3.times do
article_tag = #article.article_tags.build()
end
end
And my form for articles is currently like this (I have gone back and forth between nesting the fields_for :tags inside the fields_for :article_tags or just letting them be on their own):
<%= form_for #article , :html => { :multipart => true } do |f| %>
...excerpted...
<%= f.fields_for :article_tags do |t| %>
<%= t.fields_for :tags do |ta| %>
<%= ta.label :tag_name, "Tag name" %>
<%= ta.text_field :tag_name %>
<% end %>
<% end %>
I realize this is probably messy; I'm pretty new at this and I'm trying to figure it out. Do I have to add anything to the articles_controller create? Is it something to do with the attr_accessible? Or should I do something completely different?
EDIT:
Here are the request parameters after making the change suggested by Hck and creating a new article, selecting an existing tag with tag_id 3 and trying to also create a new tag at the same time:
Started POST "/articles" for 127.0.0.1 at 2011-08-10 19:05:46 +1000
Processing by ArticlesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"5CQuV4RWfFZD1uDjv1DrZbIe+GB/sDQ6yiAETZutmZ4=", "article"=>{"heading"=>"Test heading", "content"=>"Test Content", "tag_ids"=>["3"], "article_tags"=>{"tags"=>{"tag_name"=>"Test tag"}}}, "commit"=>"Submit"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
WARNING: Can't mass-assign protected attributes: article_tags
Tag Load (0.4ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" = 3 LIMIT 1
AREL (0.4ms) INSERT INTO "articles" ("content", "user_id", "created_at", "updated_at", "heading", "image_file_name", "image_content_type", "image_file_size") VALUES ('Test Content', 1, '2011-08-10 09:05:46.228951', '2011-08-10 09:05:46.228951', 'Test heading', NULL, NULL, NULL)
AREL (0.2ms) INSERT INTO "article_tags" ("article_id", "tag_id", "created_at", "updated_at") VALUES (88, 3, '2011-08-10 09:05:46.243076', '2011-08-10 09:05:46.243076')
[paperclip] Saving attachments.
Redirected to [localhost]
Completed 302 Found in 212ms
And if I add :article_tags to the attr_accessible for Article and try again, I get:
Started POST "/articles" for 127.0.0.1 at 2011-08-10 19:11:49 +1000
Processing by ArticlesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"5CQuV4RWfFZD1uDjv1DrZbIe+GB/sDQ6yiAETZutmZ4=", "article"=>{"heading"=>"Test heading", "content"=>"Test content", "tag_ids"=>["3"], "article_tags"=>{"tags"=>{"tag_name"=>"Test tag "}}}, "commit"=>"Submit"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Tag Load (0.4ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" = 3 LIMIT 1
Completed in 119ms
ActiveRecord::AssociationTypeMismatch (ArticleTag(#2165285820) expected, got Array(#2151973780)):
app/controllers/articles_controller.rb:32:in `create'
Try to replace #article.article_tags.build.build_tag with #article.tags.build in your controller`s action.
I don't think you have to nest the article tags in it too. Article tags is just an association between the articles and tags. You can simply create the new tag within the articles because you already associated with them. I believe it is from the magic of the "accepts_nested_attributes". try this.
<%= form_for #article , :html => { :multipart => true } do |f| %>
...excerpted...
<%= f.fields_for :tags, Tag.new do |t| %>
<%= t.label :tag_name, "Tag name" %>
<%= t.text_field :name %>
<% end %>
<% end %>
Also, you should try to mass assign it instead of saving every attribute piece by piece by using private params. I had a nested forms problem before too, so you can take a look at how I wrote my code:
Cannot save record to database RAILS nested forms
The only thing I left out there was the private params section, which I recommended you to do.
private
def venue_params
params.require(:venue).permit(:name, :address, :discount, :latitude, :longitude, :tags_attributes =>[:name],:tag_ids => [])
end
I also wrote a blog post about nested forms, so you can take a look at it too
http://minling.github.io/
I have a fixtures and a teams database to show football fixtures.
I can currently view all fixtures(index & show..) and all teams(index only).
create_table "fixtures", :force => true do |t|
t.integer "home_id"
t.integer "away_id"
t.date "date"
...
create_table "teams", :force => true do |t|
t.string "name"
...
My Fixture & Team models are below;
class Fixture < ActiveRecord::Base
belongs_to :home, :class_name => 'Team'
belongs_to :away, :class_name => 'Team'
...
class Team < ActiveRecord::Base
has_many :fixtures
...
I get the following error when trying to display a teams fixtures on 'teams#show' page
Processing by TeamsController#show as HTML
Parameters: {"id"=>"3"}
Team Load (0.4ms) SELECT "teams".* FROM "teams" WHERE ("teams"."id" = 3) LIMIT 1
Fixture Load (0.3ms) SELECT "fixtures".* FROM "fixtures"
Fixture Load (0.3ms) SELECT "fixtures".* FROM "fixtures" WHERE ("fixtures".team_id = 3)
SQLite3::SQLException: no such column: fixtures.team_id: SELECT "fixtures".* FROM "fixtures" WHERE ("fixtures".team_id = 3)
I understand the error is saying it's looking for team_id in the fixtures but that doesn't exist as I have a home_id and away_id as each fixture always contains two teams, so how do I build a find to display an individual teams fixtures?
I wish there was a good way of doing this too, but there really isn't. Probably the best workarounds are:
To have two has_manys, :home_fixtures and :away_fixtures, and then a method fixtures that returns the union of the two (see this question for more details).
To skip out on the has_manys altogether and just write a method fixtures that gives you what you want without actually having and ActiveRecord relationship set up.
Like so (Rails 2.3-Style):
def fixtures
return Fixture.all(:conditions => ['away_id = :id OR home_id = :id', {:id => self.id}])
end
Hope this helps!