Polymorphic Associations in Rails for different 'author' models - ruby-on-rails

How would a Polymorphic Association (here: Comments) be itself associated with different types of Authors?
Starting from …
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => :true
end
… I'd need to have one author being from model Hosts, ID 5 and another one from Users, 2.
How could path helpers look like…
<%= link_to comment.author.name, user_path(comment.author) %>
… when "user_path" or "host_path" are dynamic, depending on the author model?
EDIT***
There are Events, Places etc. that can have comments like so:
has_many :comments, :as => :commentable
To the polymorphic Comment model i would like to add IDs and Types to refer to Authors of comments:
create_table "comments", :force => true do |t|
t.text "content"
t.integer "commentable_id"
t.string "commentable_type"
t.integer "author_id"
t.string "author_type"
end
An Events page displays comments, and clicking on an author name should take me either to User(5) oder AnotherModel(2), depending on who wrote the comment.
I'd like to know how everybody handles this kind of situation. Should I think about adding a second polymorphic "middle layer", such as "profile", that could hold the subclasses "User", "Host" and so forth?
EDIT 2
Having only one User model would make life easier here obviously, but that cannot be done for other reasons. And in general i'm interested how this could be organized well.

Simply putting
<%= link_to comment.author.name, comment.author %>
should do the trick. If you want more flexibility, I would suggest
link_to ..., action_in_host_path(comment.author) if comment.author.kind_of? Host

This is what I've used in the past (in a view helper), but it uses an eval:
def model_path(model, options = {})
{:format => nil}.merge(options)
format = (options[:format].nil? ? 'nil' : "'#{options[:format].to_s}'")
eval("#{model.class.to_s.downcase}_path(#{model.id}, :format => #{format})")
end
Use like this:
<%= link_to comment.author.name, model_path(comment.author) %>

Would polymorphic_url help?
<%= link_to comment.author.name, polymorphic_url(comment.commentable)
%>

Related

Button to store data from one table to another in rails

I have a funding table in which there is a field name_of_organisation which displays a list of organisations from organisation table. In the list I have an option "Not Listed". If notlisted is selected a further form opens to add organisation. But that organisation details get added in the funding table. What I want is on funding show page if new organisation details are added admin can verify the organisation details and should have a button to click on so that all those organisation details in funding table get added in the organisation table. I need help with query for add organisation button on show.html.erb page for funding to add organisation details from funding to organisation table.
schema for funding table
create_table "fundings", force: :cascade do |t|
t.string "type_of_activity"
t.string "season"
t.text "activity_details"
t.string "name_of_organisation"
t.string "city"
t.string "province"
t.string "postal_code"
t.string "telephone_number"
t.text "address"
t.integer "organisation_id"
end
schema for organisation table
create_table "organisations", force: :cascade do |t|
t.string "name_of_organisation"
t.text "address"
t.string "city"
t.string "province"
t.string "postal_code"
t.string "telephone_number"
end
Show.html.erb (funding)
<%unless #funding.organisation.blank?%>
<p><strong>Name of the Organisation:</strong>
<%= #funding&.organisation&.name_of_organisation %></p><br>
<%end%>
<% if #funding.name_of_organisation.present? %>
<%= #funding.name_of_organisation %>
<% if current_user.superadmin? %>
<%= link_to 'Add Organisation', '' %>
<% end %>
<% end %>
You should use the ID of the organization instead of the name to link the two models together. Thats how associations in ActiveRecord work.
Your attempt has a huge flaw in that editing the name of the organization would break any assocation. IDs don't change.
Start by generating a migration to add a foreign key column to the fundings table:
# rails g migration add_organization_to_fundings organization:belongs_to
how class AddOrganizationToFundings < ActiveRecord::Migration[5.0]
def change
add_reference :fundings, :organization, foreign_key: true
end
end
If you have any existing data that is linked by the name you need to iterate through and fix these records:
Funding.find_each(batch_size: 100) do |f|
organization = Organization.find_by(name: f.name_of_organisation)
f.update!(organization_id: organization.id)
end
You should then write a migration to remove the fundings.name_of_organisation column.
You then need to setup the proper assocations:
class Funding < ApplicationRecord
belongs_to :organization
end
class Organization < ApplicationRecord
has_many :fundings
end
You can then create a select tag by using the form option helpers:
<%= form_for(#funding) do |f| %>
<%= f.select :organization_id, Organization.all, :id, :name %>
<% end %>
I know it's infuriating when you ask "How do I do this?" on stackoverflow and they say "Don't", but in this instance it's pretty cut and dry. Don't do it this way.
You're storing the organization name and address and such twice, once for the funding and once for the organization. This is a very bad practice, which will bite you when it comes time to change anything at all. You want to store the organization details the organization table, and that table only. Strip it out of your funding table, and add a foreign key to reference which organization it belongs to.
These are pretty basic concepts, you really want to take a little time and learn about databases before going further. Find a decent lecture, article, or tutorial on "relational database basics" and work through it. It doesn't have to be (and probably won't be) specific to rails.
After that, read up on Activerecord, especially relations.
To solve your admin approval problem, what I would do is add an "approved" boolean flag to the organization table and a custom controller action only available to admins to approve it. You can go further and create an "approved" scope for normal users, and a "pending" scope or something visible to admins. That's only one possible solution of many, though.
In order to 'Add Organisation' .. I would run get the path to the method of logic you're trying to do and remote:true the logic depending if you're planning to render with js or redirect to a new screen.

Limit association based on rich relation attributes

first question for me here! Im trying to assign 'key companies' to my users. These are found in a many-to-many rich join table. On this table there are attributes like new, key, active and so forth. I want to assign companies to users in a long list and for that Im using SimpleForm.
Everything is working excepts that I want to filter out and limit the association relation based on the attributes on the rich relation. I have company relations for each user but not all of them are akey-relation or a new-relation for example. I only want the association being key to show up and not touch the other ones. I also want to set the attribute active to true when Im assigning these companies to the users. My code looks like this now:
user.rb
class User < ActiveRecord::Base
has_many :company_user_relationships
has_many :companies, through: :company_user_relationships
company.rb
class Company < ActiveRecord::Base
has_many :company_user_relationships
has_many :users, through: :company_user_relationships
schema.rb
create_table "company_user_relationships", force: true do |t|
t.integer "company_id"
t.integer "user_id"
t.boolean "key"
t.boolean "active"
t.datetime "last_contacted"
t.string "status_comment"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "status"
t.boolean "new"
end
users_controller.rb
def assign_key_companies
User.update(params[:users].keys, params[:users].values)
redirect_to(:back)
end
view
= form_for :user_companies,
url: assign_key_companies_users_path,
html: {:method => :put} do |f|
- users.each do |user|
= simple_fields_for "users[]", user do |u|
tr
td.col-md-4
= "#{user.first_name} #{user.last_name}"
td.col-md-8
= u.association :companies, label: false, collection: #key_company_candidates,
input_html: {data: {placeholder: " Assign key companies"}, class: 'chosen-select'}
= submit_tag "Save key companies", class: "btn btn-success pull-right"
I basically want to only show user.companies.where(key: true) and the SQLCOMMIT to always put the key-field to true when updating the record.
How can i filter out to only affect the associations I want?
I can think of two ways.
First to filter it at the association level
class User < ActiveRecord::Base
has_many :company_user_relationships, -> { where("company_user_relationships.key" => true) }
Or a where
user.companies.where("company_user_relationships.key" => true)
When you call user.companies it actually doing the join table among all three tables, so you could specify the condition like my example.

Select hidden and label from enum

project.rb
class Project < ActiveRecord::Base
has_many :details, dependent: :destroy
accepts_nested_attributes_for :details, allow_destroy: true
end
details.rb
class Details < ActiveRecord::Base
belongs_to :project
enum question: {
0: "Question 1...",
1: "Question 2..."
}
end
details table
create_table "details", force: :cascade do |t|
t.integer "project_id"
t.integer "question"
t.string "answer"
end
project form
= form_for #project do |f|
...
%h3 Questions
= f.fields_for :details do |d|
.nested-fields
.field
= d.label (question value here)
= d.hidden_field (question ID here)
= d.text_field :answer
%br/
...
.actions
= f.submit "Submit"
Basically my project has a details table that is suppose to store a collection of 5 questions. Those questions are static and will always be the same. What I thought as a solution was to make and enum, and store in the details table the ID of the question and a string with the answer. But now I'm stuck with the form and I would like some help on how can make a form to create and edit the project details. On the form code you can find exactly what I'm trying to do in pseudo-code. I would also be open to a different implementation solution. Thanks!
You use ActiveRecord::Enum in a wrong way. It is meant to map values to integers:
enum question: {question1: 1, question2: 2},
while you're using it to map integers to values. I'd suggest that you create a simple accessor method:
def questions(index)
["Question1", "Question2"][index]
end
So that you can pick a question like that:
Detail.questions(n)
By the way, if you use Detail just for holding theses questions, get rid of it and put the above method in Project.

How to Nest Models within a Model

Imagine I have two models
Film
-name
-description
-duration
-year_made
-rating
-actors
Actor
-name
-d_o_b
-biography
-films
Actors are nested in a Film and vice versa.
How do I represent this relationship in my Ruby models? Realistically I would have a third table mapping actor_id with film_id.
Whilst adding details to a film I would like to be able to create an actor on the fly(if an actor does not exist create a new one with the name supplied)
Thank you in advance.
ADDITION:
Just found a link to a similar question.
You're looking at a Has and Belongs to Many (HABTM) relationship between the two tables.
Read about HABTM relationship in the Rails guides here: http://edgeguides.rubyonrails.org/association_basics.html#has_and_belongs_to_many-association-reference
First you'll need to generate a migration which will look something like this:
class AddActorFilmTable < ActiveRecord::Migration
def self.up
create_table :actors_films, :id => false do |t|
t.integer :actor_id, :null => :false
t.integer :film_id, :null => :false
end
add_index :actors_films, [:actor_id, :film_id], :unique => true
end
def self.down
drop_table :actors_films
end
end
and then specify in your models:
class Actor < ActiveRecord::Base
has_and_belongs_to_many :films
end
class Film < ActiveRecord::Base
has_and_belongs_to_many :actors
end
This will allow you to use all of the additional Rails methods for this type of relationship. To use this in a form, you could follow RailsCast 17: HABTM Checkboxes - though it's old, it should still apply. Alternatively, you can use a gem like Simple Form to easily generate the associations for you like so:
form_for #actor do |f|
f.collection_check_boxes :film_ids, Film.all, :id, :name
end

ROR Self referential has_many through with accept_nested_attributes_for

I wondered if someone could take a quick look at this. I'm making a simple conversion application which converts between units of measurement. I need to be able to self reference the table using a join table which stores the relationship between each, along with the conversion between each. This then would be referenced between either side of the relationship. For example 1cm = 10mm and 10mm = 1cm.
So thus far I have this:
#migrations
create_table :measures do |t|
t.string :name
end
create_table :measure_measures do |t|
t.integer :measure_id
t.integer :related_measure_id
t.integer :conversion
end
class Measure < ActiveRecord::Base
has_many :related_measures,
:foreign_key => 'measure_id',
:class_name => 'MeasureMeasure',
:dependent => :destroy
has_many :measures, :through => :related_measures
accepts_nested_attributes_for :related_measures,
:reject_if => proc { |attrs| attrs['related_measure_id'].blank? ||
attrs['quantity'].blank? },
:allow_destroy => true
end
#controller
#measure = Measure.find params[:id
#form
<% form_for #measure do |f| %>
<% fields_for :related_measures do |f_r_m| %>
<%= f_r_m.text_field :related_measure_id -%>
<%= f_r_m.text_field :quantity -%>
<% end %>
<% end %>
For the most part this works ok. Except I cannot access the name of the related measure, only the owner.
I need to get it somehow like this:
f_r_m.object.related_measure.name
but clearly despite my best efforts i cannot set it up and receive the error.
undefined method `owner_measure' for #<MeasureMeasure:0x1053139a8>
Help would be very much appreciated. :)
At first glance the problem comes from the has many through definition. By failing to define the foreign key, Rails, assumes measure_id in the join table. Which would just link back to the measure you're trying to find it from.
has_many :measures, :through => :related_measures, :foreign_key => :related_measure_id
We can't diagnose this error without seeing the innards of the join model.
f_r_m.object.related_measure refers to join table.
But I suspect it's because you haven't defined the relationship properly in the join model. Which should look something like this:
class MeasureMeasures < ActiveRecord::Base
belongs_to :measure
belongs_to :related_measure, :classname => "Measure"
end

Resources