I have users and books. It's a many to many relationship, so I created a join table based on questions and answers I found on this site.
class Book < ApplicationRecord
has_and_belongs_to_many :users
end
class User < ApplicationRecord
has_and_belongs_to_many :books
end
class BooksUsersJoinTable < ActiveRecord::Migration[5.0]
def change
create_table :books_users_join_table, :id => false do |t|
t.integer :book_id, index: false
t.integer :user_id, index: false
end
end
add_index(:books_users, [:book_id, :user_id], :unique => true)
add_index(:books_users, :book_id)
add_index(:books_users, :user_id)
end
So I suppose my questions are:
Will this join_table work how I have it or is there a better way to have written it?
I just wrote the add_index lines in this file. Do I have to create indexes from the command line and if so, how do I do that?
How do I use this join_table in a controller?
This is what I use do a join
class BooksController < ApplicationController
def index
#books Book.all.joins(:user)
end
end
I hope that this helps
Related
Good afternoon. I'm new to rails and I'm using google translate to post in English here, so sorry if it's not very readable.
My question is, I have a User table, and a Setting table.
They are related (but I don't know if the relationship is correct), they can even confirm me, and I would like to know if:
when creating a user, I would like to automatically change the "email" and "push" fields of that user's settings table to true.
Would it be possible via a method that in the user model called: "setting_default"?
User model.
class User < ApplicationRecord
has_one :setting
before_save :setting_default
def setting_default
self.setting.update(:email, 'true')
self.setting.update(:push, 'true')
end
Setting Model
class Setting < ApplicationRecord
has_one :user
end
The Controller is normal, if you need it, I can put it in the post
My migration:
class CreateSettings < ActiveRecord::Migration[6.0]
def change
create_table :settings do |t|
t.boolean :email, default: true
t.boolean :push, default: true
t.timestamps
end
end
end
class AddSettingsToUser < ActiveRecord::Migration[6.0]
def change
add_reference :users, :setting, null: true, foreign_key: true
end
end
Google translate has worked well for you here.
First off you'll want to change your Setting model to belong to the User:
class Setting < ApplicationRecord
belongs_to :user
end
Your settings DB table is missing a user_id field to tie the setting back to the user. I'm not used to the add_reference technique so I just do things myself in the migrations. This would work:
class CreateSettings < ActiveRecord::Migration[6.0]
def change
create_table :settings do |t|
t.integer :user_id
t.boolean :email, default: true
t.boolean :push, default: true
t.timestamps
end
end
end
(Make note that your users DB table has a field setting_id that it does not need. I don't think it should be there. I would remove it. Unless it's a Rails 6 thing I'm not used to.)
Next it would probably be better to assign the values if the save succeeds (and not if it fails) so you'll want an after_save instead. And I'm simplifying your value assignment just in case you're having an issue there:
class User < ApplicationRecord
has_one :setting
after_save :setting_default
def setting_default
setting.email = true
setting.push = true
setting.save(validate: false)
end
private :setting_default
And to answer what seems to be your question, yes, what you're trying to do should be easily possible. This is a very common thing to do. It should work.
When you use one-to-one association you need to choose has_one in one and belongs_to in another model
Semantically user has one setting, but not setting has one user
So it's better to reverse them
To change your schema you need to write new migration
class ChangeOneToOneDirection < ActiveRecord::Migration[6.0]
def up
change_table :settings do |t|
t.belongs_to :user, foreign_key: true, null: false
end
User.where.not(setting_id: nil).find_each |user|
Setting.find(user.setting_id).update_columns(user_id: user.id)
end
change_table :users do |t|
t.remove :setting_id
end
end
def down
add_reference :users, :setting, null: true, foreign_key: true
Setting.find_each do |setting|
User.find(setting.user_id).update_columns(setting_id: setting.id)
end
change_table :settings do |t|
t.remove :user_id
end
end
end
After migration you can change User model
class User < ApplicationRecord
has_one :setting
after_commit :setting_default
private
def setting_default
setting&.update(email: true, push: true)
end
end
It's better to update associated model only if saves are in the database. And user can haven't setting. That's why after_commit and safe-navigator &
I have a self-referential relationship set up in my app so that an AppForm can belong_to another AppForm through Variations.
The behavior I am trying to create is a button on the AppForm's show page that can be clicked to create a new variation of the AppForm. This button would ideally create a duplicate of the current record and take the user to that new record's edit page to make changes before saving it.
I have the variations table set up like this in my schema.rb:
create_table "variations", force: :cascade do |t|
t.bigint "app_form_id"
t.bigint "original_app_form_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id"
t.index ["app_form_id", "original_app_form_id"], name: "index_variations_on_app_form_id_and_original_app_form_id", unique: true
t.index ["app_form_id"], name: "index_variations_on_app_form_id"
t.index ["original_app_form_id"], name: "index_variations_on_original_app_form_id"
t.index ["user_id"], name: "index_variations_on_user_id"
end
With this as my variation.rb model:
class Variation < ApplicationRecord
belongs_to :user
belongs_to :app_form
belongs_to :original_app_form, class_name: "AppForm"
end
And this in my app_form.rb model:
class AppForm < ApplicationRecord
belongs_to :application_status, optional: true
belongs_to :final_decision, optional: true
belongs_to :funding_status, optional: true
belongs_to :user, optional: true
belongs_to :app_form, through: :variations
end
This seems pretty standard for a has_many :through relationship, but now I can't seem to Google my way to creating the duplicate variation of an AppForm. I've seen posts like this one that show a variety of different ways to do this. Some use routes, others need special gems...I'm lost. What's the "Rails-y" way to do this properly?
There is no accepted "Rails-y" way to this. The Rails conventions don't actually cover every possible use case that you can find for the framework. If you can't find a "Rails-y" way there is a very good chance that it does not exist.
But this is something that's relatively straight forward to build with a nested route and nested resources.
# config/routes.rb
resources :app_forms do
resources :variations, only: [:new, :create]
end
class AppForm < ApplicationRecord
# ...
def reproducable_attributes
attributes.except('id', 'created_at', 'updated_at')
end
end
class Variation < ApplicationRecord
accepts_nested_attributes_for :app_form
# ...
# factory method to create a new instance from an existing AppForm
def self.of(original_app_form)
new(
original_app_form: original_app_form,
app_form_attributes: original_app_form.reproducable_attributes
)
end
end
class VariationsController < ApplicationController
before_action :set_original_app_form
# GET /app_forms/:app_form_id/variations/new
def new
#variation = Variation.of(#original_app_form)
end
# POST /app_forms/:app_form_id/variations
def create
#variation = #original_app_form.variations.new(variation_params) do |v|
v.user = current_user
end
if #variation.save
redirect_to #variation.app_form
else
render :new
end
end
private
def set_original_app_form
#original_app_form = AppForm.find(params[:app_form_id])
end
def variation_params
params.require(:variation)
.permit(app_form_attributes: [:foo, :bar])
end
end
<%= form_for([#original_app_form, #variation]) do |f| %>
<%= f.fields_for :app_form do |app_form_fields|%>
# ...
<% end %>
# ...
<% end %>
I have 3 tables: proposals, items/proposals (items is nested inside proposals) and invoices.
I want to create invoices for those items in the proposals that got approved. How would the associations for these look like? Also, how would I set up the invoices form to choose only those items that got approved by the client?
Consider creating two different line items models for Proposal and Invoice.
class Proposal < ActiveRecord::Base
has_many :proposal_line_items
end
class ProposalLineItem < ActiveRecord::Base
belongs_to :proposal
end
class Invoice < ActiveRecord::Base
has_many :invoice_line_items
end
class InvoiceLineItem < ActiveRecord::Base
belongs_to :invoice
end
You can consider having an "approved" attribute in proposal line items. In the invoice form, you can show proposal line items approved by the client.
The suggestion of having separate line items for Proposal and Invoice is based on ERP data modeling principles to maintain the integrity of Invoice.
Update
For example here are the sample migrations for the models suggested
class CreateProposalLineItems < ActiveRecord::Migration
def change
create_table :proposal_line_items do |t|
t.references :proposal, index: true, foreign_key: true
t.string :name
t.integer :approved
t.timestamps null: false
end
end
end
class CreateProposals < ActiveRecord::Migration
def change
create_table :proposals do |t|
t.string :name
t.timestamps null: false
end
end
end
class InvoicesController < ActionController
def new
#approved_items = Proposal.find(params[:proposal_id]).proposal_line_items.where(:approved => 1)
end
end
You can iterate over the #approved_items in your view and display it to users.
V
I am in quite the pickle , I think I overreach with my current ruby knowledge but I don't want to give up.
I currently have a tweeter that can post and people can follow other people thanks to https://www.railstutorial.org/book/ . I do want to add hashtags to this tutorial tweeter. In order to do I created 2 tables since tweet and hashtag is a many to many relationship . The tables are :
class CreateHashrelations < ActiveRecord::Migration
def change
create_table :hashrelations do |t|
t.integer :tweet_id
t.integer :hashtag_id
t.timestamps null: false
end
add_index :hashrelations, [:tweet_id, :hashtag_id], unique: true
end
end
which is the extra table you need to keep the keys of the tweet and hashtag . And the other table is the hashtag table where I have the id and the name of the hastag
class CreateHashtags < ActiveRecord::Migration
def change
create_table :hashtags do |t|
t.string :name
t.timestamps null: false
end
end
end
In the models I put the following relathipships:
class Hashtag < ActiveRecord::Base
has_many :hashtagrelations, dependent: :destroy
has_many :tweets, through: :hashtagrelations
validates :name, presence: true
end
class Hashrelation < ActiveRecord::Base
belongs_to :tweet
belongs_to :hashtag
validates :tweet_id, presence: true
validates :hashtag_id, presence: true
end
class Tweet < ActiveRecord::Base
.....
has_many :hashtagrelations, dependent: :destroy
has_many :hashtags, through: :hashtagrelations
....
end
When a tweet is submited I save it and if it is saved I want to see if it has hashtags and if it does I want to add the necessary data in the Hashtagrelations and Hashtags tables.
I try to do this this way :
class TweetsController < ApplicationController
......
def create
#tweet = current_user.tweets.build(tweet_params)
if #tweet.save
add_hashtags(#tweet)
flash[:success] = "Tweet created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
......
private
........
def add_hashtags(tweet)
tweet.content.scan(/(?:\s|^)(?:#(?!(?:\d+|\w+?_|_\w+?)(?:\s|$)))(\w+)(?=\s|$)/){ |tag|
newhash[:new] = tag
#hashtag = Hashtag.new(new_hash[:new])
#hashtag.save
data[:new] = [tweet.id,#hashtag.id]
#hashrel = Hashtagrel.new(data[:new])
#hashrel.save
}
end
end
Which is not the right way. I tried to add the newhash and data because if I only did tag there I would get
When assigning attributes, you must pass a hash as an argument.
I realise that this is kind of a silly question but I have found no tutorial that teaches me how should I add this data to my tables. I would be grateful for your help
This is an array of values:
data[:new] = [tweet.id,#hashtag.id]
Before moving things inside another variable (or data structure), try being explicit first.
#hashrel = Hashtagrelation.new(tweet_id: tweet.id, hashtag_id: #hashtag.id)
The rest of the code looks good, I think you've got it.
I have a Genre model, and I want both videos to have many genres and profiles to have many genres. I also want genres to have many videos and genres to have many profiles. I understand the polymorphic and join table stuff, so I'm wondering if my code below will work as I intend it to. Also, I'd appreciate any advice on how to access things in my controller and views.
This is what I envision that the join table should look like (I don't think I need an elaborate :has :through association because all I need in the join table are the associations and nothing else, so the table won't have a model):
genres_videos_profiles:
-----------------------------------------------------
id | genre_id | genre_element_id | genre_element_type
Here's my genre.rb:
has_and_belongs_to_many :genre_element, :polymorphic => true
Here's video.rb:
has_and_belongs_to_many :genres, :as => :genre_element
Here's profile.rb:
has_and_belongs_to_many :genres, :as => :genre_element
Will this work as I intend it to? I'd like some feedback.
As far as I know HABTM associations can´t be polymorphic, I couldn´t find an example like yours in the API documentation. If you want only join tables, your code could look like this:
class Genre
has_and_belongs_to_many :videos
has_and_belongs_to_many :profiles
end
class Video
has_and_belongs_to_many :genres
end
class Profile
has_and_belongs_to_many :genres
end
And access it like Mike already wrote:
#genre.profiles
#profile.genres
#genre.videos
#video.genres
Migrations (for join tables only):
class CreateGenresVideosJoinTable < ActiveRecord::Migration
def self.up
create_table :genres_videos, {:id => false, :force => true} do |t|
t.integer :genre_id
t.integer :video_id
t.timestamps
end
end
def self.down
drop_table :genres_videos
end
end
class CreateGenresProfilesJoinTable < ActiveRecord::Migration
def self.up
create_table :genres_profiles, {:id => false, :force => true} do |t|
t.integer :genre_id
t.integer :profile_id
t.timestamps
end
end
def self.down
drop_table :genres_profiles
end
end
I think that has_and_belongs_to_many can be a bit difficult to follow when it comes to polymorphic (if it even works). So if you want to do the polymorhpic thing, then you can't use any "through" syntax:
class Genre < ActiveRecord::Base
has_many :genres_videos_profiles
end
class GenresVideosProfile
belongs_to :genre
belongs_to :genre_element, :polymorphic => true
scope :videos, where(:genre_element_type => "Video")
scope :profiles, where(:genre_element_type => "Profile")
end
And then you use it like:
# All genre elements
#genre.genres_videos_profiles.each do |gvp|
puts gvp.genre_element.inspect
end
# Only video genre elements
#genre.genres_videos_profiles.videos.each do |gvp|
puts gvp.genre_element.inspect
end
Check out that: http://blog.hasmanythrough.com/2006/4/3/polymorphic-through
For me it was perfect and clean!