many-to-many with multiple select in Rails - ruby-on-rails

I have a model called Article, another model called Organization, and a join table called ArticleAllowedOrganization that has an article_id and organization_id in it.
When a user creates an article I would like for them to specify which organizations should have access to the article.
When I test in the console it seems to be working correctly, however, when I create an article in the web app the params for the through association allowed_organization_ids is empty.
Article model:
class Article < ActiveRecord::Base
has_many :article_allowed_organizations, dependent: :destroy
has_many :allowed_organizations, through: :article_allowed_organizations, source: :organization
end
Organization model:
class Organization < ActiveRecord::Base
has_many :article_allowed_organizations, dependent: :destroy
has_many :allowed_articles, through: :article_allowed_organizations, source: :article
end
the join table:
class ArticleAllowedOrganization < ActiveRecord::Base
belongs_to :article
belongs_to :organization
end
in the articles_controller.rb I permitted the array for allowed_organization_ids
def article_params
params.require(:article).permit(:other_stuff, :allowed_organization_ids => [])
end
In the form:
<%= f.collection_select(:allowed_organization_ids, Organization.order(:name), :id, :name,
{include_blank: true}, {multiple: true}) %>
In the console I can manually set multiple organization id's to be associated with an article and it works.
a = Article.last
a.allowed_organization_ids = [2, 4]
you can see below that it inserts the ids for an organization into the ArticleAlllowedOrganization table when I use the console.
(0.3ms) BEGIN
SQL (1.3ms) INSERT INTO "article_allowed_organizations" ("article_id", "organization_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["article_id", 95], ["organization_id", 4], ["created_at", "2018-12-12 17:47:25.978487"], ["updated_at", "2018-12-12 17:47:25.978487"]]
(6.5ms) COMMIT
=> [2, 4]
when using the web app I get this in the log where the allowed_organization_ids isn't passing anything.
Parameters: {"....some_other_params" "allowed_organization_ids"=>[""]}
if I try and use collection_check_boxes
<%= f.collection_check_boxes :allowed_organization_ids, Organization.all, :id, :name do |b| %>
<div class="collection-check-box">
<%= b.check_box %>
<%= b.label %>
</div>
<% end %>
I get this in the log when using collection_check_boxes where it says true instead of the organization ids
"allowed_organization_ids"=>["true"]
also what's interesting is that I also get the below in th elog when trying to use checkboxes where it's trying to find an organization that has an id of 0 but can't which prevents the form from submitting.
[1m[35mOrganization Load (0.5ms)[0m SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT 1 [["id", 0]]
Completed 404 Not Found in 67ms (ActiveRecord: 2.4ms)
ActiveRecord::RecordNotFound (Couldn't find Organization with 'id'=0):

Please check follow are few issues. In your controller, you put
def article_params
params.require(:article).permit(:other_stuff, :allowed_organization_ids => [])
end
So you are already saying your allowed_organization_ids is empty, please replace with following
def article_params
params.require(:article).permit(:other_stuff, :allowed_organization_id)
end
I think you are just changing the name, so you have provided the different name of classes but here in the relationship you are mentioning different, but as your console is giving correct relationship this should not be an issue, but in case I am mentioning it.
class ArticleAllowedOrganization < ActiveRecord::Base
belongs_to :article
belongs_to :organization
end
should be allowed_article and allowed_organization
I am practicing so I created this in the project to see if it is working with proper names, so following are commands I used in the generator to create this whole project. You can try to see if there is any other error. Note I put permission field in the allowed table, all commands are on command line inside your project first command will create new project.
rails new SampleProject && cd $_ && bundle
rails g model Article title:string description:text
rails g model Organization name:string
rails g model Allowed permission:string article:references organization:references
Now make sure your Article model has following.
class Article < ActiveRecord::Base
has_many :alloweds
has_many :organizations, through: :alloweds
end
And in your Organization model
class Organization < ActiveRecord::Base
has_many :alloweds
has_many :articles, through: :alloweds
end
And in your allowed model, although I will use permission than allowed table name but it is your naming.
class Allowed < ActiveRecord::Base
belongs_to :article
belongs_to :organization
end
Now let us generate views and controllers
rails g scaffold_controller Article title:string description:string
rails g scaffold_controller Organization name:string
rails g scaffold_controller Article title:string description:string
Now put routes in your config/routes.rb
resources :articles
resources :organizations
resources :alloweds
Now you can start your rail server and navigate to /articles , /organization and /alloweds to see it working. Rails auto generator uses text field for relationship ids, so you can put manually ids and it will work.
Hope it is clear.

Related

Querying a join table?

I am trying to do two things:
query an attribute from an inner join table in Rails' Console.
query and displaying the attribute in a view.
These are my Models:
retreat.rb:
class Retreat < ApplicationRecord
belongs_to :user
belongs_to :account
validates :name, presence: true
has_many :retreats_teams
has_many :teams, through: :retreats_teams
accepts_nested_attributes_for :retreats_teams
end
retreats_team.rb:
class RetreatsTeam < ApplicationRecord
belongs_to :team
belongs_to :retreat
end
team.rb:
class Team < ApplicationRecord
belongs_to :account
has_many :team_members
has_many :users, through: :team_members
accepts_nested_attributes_for :team_members
has_many :retreats
has_many :retreats, through: :retreats_teams
end
In Rails' console, if I type:
Retreat.last.teams
I get the output:
irb(main):008:0> Retreat.last.teams
Retreat Load (0.9ms) SELECT "retreats".* FROM "retreats" ORDER BY "retreats"."id" DESC LIMIT $1 [["LIMIT", 1]]
Team Load (0.9ms) SELECT "teams".* FROM "teams" INNER JOIN "retreats_teams" ON "teams"."id" = "retreats_teams"."team_id" WHERE "retreats_teams"."retreat_id" = $1 LIMIT $2 [["retreat_id", 38], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Team id: 56, name: "My house", account_id: 2, created_at: "2020-02-10 15:57:25", updated_at: "2020-02-10 15:57:25">]>
irb(main):009:0>
How do I retrieve the team name: "My house"?
Also, there might be many teams that display here, too.
#teams returns a collection of team objects. The simplest solution is to call first on the teams to get the first team in the collection:
Retreat.last.teams.first.name
=> "My house"
But if you want all the names in teams you might use pluck. This will allow you to do this:
retreat = Retreat.last
foo = Team.create(name: 'Foo')
bar = Team.create(name: 'Bar')
retreat.teams << foo
retreat.teams << bar
retreat.teams.pluck(:name).to_sentence
=> "My house, Foo, and Bar"
A word on naming
The naming convention for join models is SingularSingular. The table should be named singular_plural. has_and_belongs_to_many is the only part of Rails that actually uses the oddball plural_plural naming scheme.
RetreatsTeam # bad
RetreatTeam # better
Even better though is to actually give your join tables meaningful names instead of just placeholder names.
1) querying an attribute from an inner join table in Rails Console.
Since the association between Retreat and RetreatsTeams in one to many you can actually only fetch aggregates. Otherwise which attribute should it fetch, from the first row, the last row or all the rows?
So for example you can do:
Retreat.joins(:retreats_teams)
.select('retreats.*', 'COUNT(retreats_teams.*) AS retreats_teams_count')
If you are storing more data on the join table that you want to display you want to iterate through the join table:
#retreat = Retreat.eager_load(retreats_teams: :teams).first
#retreat.retreats_teams.each do |rt|
puts rt.foo
puts rt.team.name
end
2) querying and displaying the attribute in a view.
In Rails you're usually just fetching records in the controller and then iterating through them in the view:
class ResortsController < ApplicationController
def show
#resort = Resort.includes(:teams).find(params[:id])
end
end
# app/views/resorts/show.html.erb
<h1><%= #resort.name %></h1>
<h2>Teams</h2>
<% if #resort.teams.any? %>
<ul>
<% #resort.teams.each do |team| %>
<li><%= team.name %></li>
<% end %>
</ul>
<% else %>
<p>This resort has no teams</p>
<% end %>

Rails Dropdown has_many through

I can't save the id's in the join table (document_configuration).
I have tree models:
document.rb
belongs_to :languages
has_many :document_configurations
has_many :document_catalogs, through: :document_configurations
accepts_nested_attributes_for :document_catalogs
accepts_nested_attributes_for :document_configurations
document_catalog.rb
has_many :document_configurations
has_many :documents, through: :document_configurations
document_configuration.rb
belongs_to :document
belongs_to :document_catalog
So, I want to get a list of all document_catalog in my document_form So, when I create a new document I can include the corresponding catalog.
This is my form:
<div class="form-group">
<%= f.select :document_catalog_ids, DocumentCatalog.all.collect {|x| [x.name, x.id]}, {}%>
</div>
Is listing the catalogs as I want.
This is my controller:
def new
#document = Document.new
#document.document_catalogs.build
end
def document_params
params.require(:document).permit(:name, :description, :document_file,
:language_id, {:document_catalog_ids=>[]}) #I tried this too: :document_catalog_ids=>[] without the {}
end
I'm just getting this error: Unpermitted parameter: document_catalog_ids and I really need to save the document_id and document_catalog_id in document_configuration model.
Another thing is: I need to add anything else in my create, update and destroy methods?
Handling of Unpermitted Keys
By default parameter keys that are not explicitly permitted will be logged in the development and test environment. In other environments these parameters will simply be filtered out and ignored.
Additionally, this behaviour can be changed by changing the config.action_controller.action_on_unpermitted_parameters property in your environment files. If set to :log the unpermitted attributes will be logged, if set to :raise an exception will be raised.
Found this in a documentation from GitHub : https://github.com/rails/strong_parameters#handling-of-unpermitted-keys

Rails after_find callback ignore query

So, I call method Article.getByTag().
Callback after_find work, but ignore query (in which get all tags by current article).
It's rails log
Started GET "/article/lorem" for 127.0.0.1 at 2014-01-22 08:51:11 +0400
Processing by ArticleController#show_tag as HTML
Parameters: {"tagname"=>"lorem"}
Tags Load (0.2ms) SELECT `tags`.* FROM `tags` WHERE `tags`.`name` = 'lorem' LIMIT 1
Article Load (0.1ms) SELECT `articles`.* FROM `articles` INNER JOIN `tag2articles` ON `tag2articles`.`article_id` = `articles`.`id` WHERE `tag2articles`.`Tags_id` = 5
start set article tags for article 3
getTags by article 3
end set article tags
Article class:
class Article < ActiveRecord::Base
attr_accessible :text, :title
attr_accessor :tags
has_many :tag2articles
after_find do |article|
logger.info 'start set article tags for article '+article.id.to_s
article.tags = Tag2article.getTags(article.id)
logger.info 'end set article tags'
end
def self.getByTag(tagname)
#tag = Tags.get(tagname)
Article.joins(:tag2articles).where('tag2articles.Tags_id' => #tag[:id]).all()
end
end
and class Tag2article
class Tag2article < ActiveRecord::Base
belongs_to :Article
belongs_to :Tags
attr_accessible :Article, :Tags
def self.getTags(article)
logger.info 'getTags by article '+article.to_s
#tags = Tag2article.joins(:Tags).where(:Article_id => article).select('tags.*')
return #tags
end
def self.getArticle(tag)
Tag2article.where(:Tags_id => tag).joins(:Article).select('articles.*')
end
end
Basically, this is all you need:
Article class:
class Article < ActiveRecord::Base
has_many :tags_to_articles
has_many :tags, through: :tags_to_articles
end
and the model class for the join table
class TagsToArticle < ActiveRecord::Base
belongs_to :article
belongs_to :tag
end
and you fetch all articles by tag name by doing:
Article.joins(:tags).where('tags.name' => tag_name)
I'm writing this from my head and I have NO idea what you managed to implement in your code so far, so please excuse me if the code doesn't work from the start - play with it and make it work.
Also, I suggest starting a new tutorial project, you reinvented so many wheels there (notice that you don't need an after_find hook because has_many :tags, through: :tags_to_articles does this for you) and did so many funny stuff (like naming attributes of a class the same name as the class it references) that you'll just burn yourself in frustration.
Also, if you use rails 4 you don't need to do attr_accessible dance, and if you don't I suggest taking a look at strong_parameters gem https://github.com/rails/strong_parameters
See: http://guides.rubyonrails.org/association_basics.html
AND TAKE SPECIAL NOTE OF NAMING CONVENTIONS - singular and plural, camelcase and hungarian notation, uppercase and lowercase, they all have their place and their meaning and the tutorials usually make a good job of taking you through it
Ruby and Rails are quite beautiful and powerful, but only if you let them. There's no benefit inherent in the language that will somehow infuse your code if you're still essentially writing Java.

Rails - column not found for defined 'has_many' relationship

I define a Post class which can link or be linked to multiple posts. To do this I added a PostLink class which specifies post_to and post_from.
I generated the PostLink class by rails g model post_link from_post:integer to_post:integer and of course rake db:migrate, and added
belongs_to :from_post, :class_name => 'Post'
belongs_to :to_post, :class_name => 'Post'
to the class.
And I also have has_many :post_links in my Post class.
I ran rails console and Post.new.post_links and got nil printed out, which is expected. However after I save a Post using
p = Post.new
p.save
and then run p.post_links, it prints out the following error message:
SQLite3::SQLException: no such column: post_links.post_id: SELECT "post_links".*
FROM "post_links" WHERE "post_links"."post_id" = 1
So anybody know why after saving it to the database post_link can not be accessed?
The has_many :post_links association in Post throws an error because it assumes the foreign key in post_links is post_id by default. Since you are using from_post_id and to_post_id, you will have to find a way to group the post_links for "from" posts and "to" posts to get the total set of post_links for a post.
One approach could be to define two associations on Post and an additional method to add the sets together:
class Post < ActiveRecord::Base
has_many :from_post_links, :class_name => 'PostLink', :foreign_key => :from_post_id
has_many :to_post_links, :class_name => 'PostLink', :foreign_key => :to_post_id'
def post_links
from_post_links + to_post_links
end
end
As another option, you could provide special sql to the association to group the sets in a single query:
class Post < ActiveRecord::Base
has_many :post_links, :finder_sql => Proc.new {
%Q{
SELECT *
FROM post_links pl
WHERE pl.to_post_id = #{id}
OR pl.from_post_id = #{id}
}
}

No Such Column - When Column Exists

Not sure how this is happening but it's saying the column doesn't exist:
SQLite3::SQLException: no such column: element.kind: SELECT COUNT(*) FROM "answers" INNER JOIN "elements" ON "elements"."id" = "answers"."element_id" WHERE "answers"."form_id" = 55 AND "element"."kind" = 6
# element.rb
class Element < ActiveRecord::Base
has_many :answers
end
# answer.rb
class Answer < ActiveRecord::Base
belongs_to :element
belongs_to :form
end
class Form < ActiveRecord::Base
has_many :answers
end
But when I run:
#form.answers.joins(:element).where(:element => {:kind => 6})
I get the sql error above. Not sure what's going on. Any thoughts on what I'm missing?
Thanks!
FYI I'm running rails 3.2.3 with ruby 1.9.3.
The table is elements rather than element as generated by the query ("element"."kind" = 6).
#form.answers.joins(:elements).where(:elements => {:kind => 6})
I would have expected the rest of the query to be generated using the nonexistent element table as well, since you used .joins(:element) instead of .joins(:elements) but perhaps Rails is pluralizing inside .joins() for a belongs_to association.
#form.answers.joins(:element).where(:elements => {:kind => 6})

Resources