Creating an ignorable polymorphic relationship - ruby-on-rails

I am looking to create a table called ignorables with ignorable_type and ignorable_id where I can store IDs of different types of objects I'd like to ignore in the UI.
How can I leverage ActiveRecord's polymorphic associations to achieve this?

I have to say that I don't 100% get your question but this would be my take
1) you would need to create the table. I am sure you know how to do this, but this is how
rails g migration CreateIgnores
#db/migrate/20180106072916_create_ignorables.rb
class CreateIgnors < ActiveRecord::Migration[5.0]
def change
create_table :ignors do |t|
t.integer :ignorable_id
t.string :ignorable_type
...
end
add_index :ignors, [:ignorable_type, :ignorable_id]
end
end
2) now you can create your models
class Ignore < ApplicationRecord
belongs_to :ignorable, polymorphic: true
end
3) what belongs to the ignorable
class Other < ApplicationRecord
has_many :ignored, as: :ignorable
end
4) now last but not list you want to do do something
class Other < ApplicationRecord
has_many :ignored, as: :ignorable
validate :belongs_to_ignored
...
private
def belongs_to_ignored
if ignored
false
end
end
end
I hope that this can lead you in the right direction

Related

Rails custom validation inside model which belongs_to another

i'm trying to learn ruby and trying to work with business rules and console through active record methods.
Here's the problem i'm facing right now, assume the following scenario:
I have 3 models:
Class User < ApplicationRecord
has_many :animals
Class Animal < ApplicationRecord
belongs_to :user
belongs_to :tipo
Class Tipo < ApplicationRecord
has_many :animals
respective migration:
User
t.string :name
t.string :doc
t.date :birth_day
Animal
t.string :name
t.decimal :mensal_cost
add_index :animals, :user_id
add_index :animals, :user_id
Tipo
t.string :tipo_animal
I want to make a validation which get the the user_id and sum the mensal_cost
of all his animals, so if this cost is higher than 1000 then he cant have more animals.
So i think i must to get the user id and sum his respectively animals.mensal_cost array
Ok now u're contextualized, ill set my code below.
PS. it's inside animal model:
#Want to get the total of a single user mensal cost
def total
user_id.each do |mensal_cost|
mensal_cost.inject(&:+)
end
#Now the custom validation itself
validate :check_mtotal
def check_mtotal
if user_id && total > 1000
errors.add(:user_id, message: 'cant add more animals')
end
end
Well, first problem is that my total isn't returning anything, so i really don't know how make it proceed to get the ammount of mensal_cost of a single user.
second... i need the first problem solve to test the second :(.
anyone can help with this?
Thank you very much for your attention
Well i figured out the solution and it's below:
User model
#Returns the total mensal cost
def max_mensal
animals.sum(:mensal_cost)
end
Animal model
#Validates the mensal ammount
validate :check_form_mammount
def check_for_mammount
if user_id && user.max_mensal > (value here)
errors.add(:mensal_cost, message: 'msg here')
end

Deleting multiple objects in HABTM reference table

Im trying to destroy multiple records in my database table where :list column has the same name, however I get an error when I click on Destroy link: Could not find table 'bookmarks_posts', it says the error is in my controller line:
if #bookmarks.destroy_all
Why is it expecting a join table? How can I change that? Also I don't want to destory anything outside the given Bookmarks table. (I am using sqlite3, if that changes anything)
My table migration:
class CreateBookmarks < ActiveRecord::Migration
def change
create_table :bookmarks do |t|
t.string :list
t.references :user, index: true, foreign_key: true
t.references :post, index: true, foreign_key: true
t.timestamps null: false
end
end
end
My controller - destroy and show:
def destroy
#list = Bookmark.find(params[:id])
#bookmarks = Bookmark.where(:list => #list.list)
if #bookmarks.destroy_all
redirect_to bookmarks_url
end
end
def show
#lists = Bookmark.where(user_id: current_user.id).where(post_id: nil)
#list = Bookmark.find(params[:id])
#bookmarks = Bookmark.where.not(post_id: nil).where(list: #list.list)
#posts = Post.where(:id => #bookmarks.map(&:post_id))
end
in my show view I use this:
<%= link_to 'Destroy', #list, method: :delete %>
My models:
class Bookmark < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :posts
end
"Why is it expecting a join table?"
Because you have specified a HABTM association between Bookmark and Post models. So when you delete a Bookmark or a Post, it wants to remove any rows in the join table that are referencing the deleted item's ID.
The problem seems to be that you're either specifying the wrong association type in your models, or you've created the wrong migration to support a HABTM association.
For discussion, let's assume your database migration above is correct, eg: you want to store a user_id and post_id in the Bookmarks table. This means that you would have the following associations:
class Bookmark < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
class User < ActiveRecord::Base
has_many :bookmarks
end
class Post < ActiveRecord::Base
has_many :bookmarks
end
If you actually need a HABTM relationship, then you need to do a migration that creates a join table.
One way to figure out what the type of association you need is to remember that if a table has the ID of another model (eg: Bookmarks table has a user_id column), then that is a belongs_to association.

How can I name the relationship between two records?

Let's say I have a model Movie. Movies can have_many of each other through an intermediary model AssociatedMovie.
How can I specify the nature of the relationship between two Movies? For any given pair of Movies, the relationship may be prequel/sequel, or remake/original, or inspired/inspired by, or related/related, etc. Right now, I can't give the relationships names.
Here's my schema and associations:
create_table "movies", force: true do |t|
t.string "title"
end
create_table "associated_movies", force: true do |t|
t.integer "movie_a_id"
t.integer "movie_b_id"
end
class Movie < ActiveRecord::Base
has_many :movies, :through => :associated_movies
end
class AssociatedMovie < ActiveRecord::Base
has_many :movies
end
And here's the query for setting each Movie's associated Movies:
def movie_associated_movies
associated_movie_ids = AssociatedMovie.
where("movie_a_id = ? OR movie_b_id = ?", self.id, self.id).
map { |r| [r.movie_a_id, r.movie_b_id] }.
flatten - [self.id]
Movie.where(id: associated_movie_ids)
end
I think I'd probably have to add movie_a_type and movie_b_type attributes to AssociatedMovie. But I'm not sure how I could specify which Movie is attached to which type.
Anyone have any ideas?
You're already half-way there with has_many :through (using an intermediary model) - this allows you to add as many extra attributes as you like.
I think your problem is down to your relationships, which I'll explain below:
#app/models/movie.rb
class Movie < ActiveRecord::Base
has_many :associated_movies, foreign_key: :movie_a_id
has_many :movies, through: :associated_movies, foreign_key: :movie_b_id
end
#app/models/associated_movie.rb
class AssociatedMovie < ActiveRecord::Base
belongs_to :movie_a, class_name: "Movie"
belongs_to :movie_b, class_name: "Movie"
end
The above will give you access to:
#movie = Movie.find params[:id]
#movie.associated_movies #-> collection of records with movie_a and movie_b
#movie.movies #-> all the movie_b objects
--
Because you're using has_many :through, rather than has_and_belongs_to_many, you'll be at liberty to add as many attributes to your join model as you need:
To do this, you just have to add a migration:
$ rails g migration AddNewAttributes
#db/migrate/add_new_attributes_________.rb
class AddNewAttributes < ActiveRecord::Migration
def change
add_column :associated_movies, :relationship_id, :id
end
end
$ rake db:migrate
-
... I apologize if this is a little off-course; however I would actually add a separate model for your relationships (considering you have them predefined):
#app/models/relationship.rb
class Relationship < ActiveRecord::Base
#columns id | movie_a_type | movie_b_type | created_at | updated_at
has_many :associated_movies
end
#app/models/associated_movie.rb
class AssociatedMovie < ActiveRecord::Base
belongs_to :movie_a, class_name: "Movie"
belongs_to :movie_b, class_name: "Movie"
belongs_to :relationship
delegate :movie_a_type, :movie_b_type, to: :relationship
end
This may seem a little bloated (it is), but it will provide extensibility.
You'll have to add another table, but it will ultimately provide you with the ability to call the following:
#movie.associated_movies.each do |associated|
associated.movie_a #-> current movie
associated.movie_b #-> related movie
associated.movie_a_type #-> "Original"
associated.movie_b_type #-> "Sequel"
end
You'd then be able to pre-populate the Relationship model with the various relationships you'll have.
I can add to the answer as required.

Rails How to use has_many_and_belongs_to_many with accepts_nested_attributes_for

I have two models:
Routes and Activity
I have a Many-To-Many relationship between them through a migration that looks like:
class ActivitiesRoutes < ActiveRecord::Migration
def up
create_table :activities_routes, :id => false do |t|
t.integer :route_id
t.integer :activity_id
end
end
end
In a rest service i get the data for a route and I get multiple activities, my models look like this:
class Route < ActiveRecord::Base
attr_accessible :activities_attributes
has_and_belongs_to_many :activities
accepts_nested_attributes_for :activities
end
and:
class Activity < ActiveRecord::Base
attr_accessible :activitytext, :iconid
has_and_belongs_to_many :routes
end
On my app controller I want to make something like :
ruta=Route.create({
#other data for the model
})
ruta.activities_attributes = #activitiesarray #Array made with the Activities received
But I get an error:
undefined method `activities_attributes' for #<Route:0x2bccf08>
If i left it like :
ruta.activities_attributes << #activitiesarray
I get:
undefined method `with_indifferent_access' for #<Activity:0x6af7400>
Does anyone know ho can I make that possible?
Thank you :)
You can't do this
ruta.activities_attributes << #activitiesarray
because accepts_nested_attributes_for only provides a *_attributes= method so the following should work
ruta.activities_attributes = #activitiesarray

Time dependent Has and Belongs-to Many

I'm working on a rails app where the associations between data change with time. I've created a model for the associations:
create_table :accounts_numbers do |t|
t.integer :number_id
t.integer :account_id
t.date :start_date
t.date :end_date
And, so far, I have a simple model
class Account < ActiveRecord::Base
has_and_belongs_to_many :numbers, :through => accounts_numbers
end
But instead of
#account.numbers
I need something like
#account.numbers_at(Date.new(2010,2,3))
I thought I could use :conditions, but I wouldn't haven't seen a way to tell has_and_belongs_to_many to create a parameterized field. I've also looked into named_scope, but that only seems to return accounts, not numbers.
More importantly, this pattern is going to cover many relationships in my code, so would there be a way to coin a time_dependent_has_and_belongs_to_many for use all over?
After much more searching, I finally found out what to do; In the /lib dir of my project, I created a module, TimeDependent:
module TimeDependent
def at(date)
find(:all, :conditions=>["start_date <= ? AND ? < end_date"], date, date)
end
end
So, my model becomes
require "TimeDependent"
class Account < ActiveRecord::Base
has_many :accounts_numbers
has_many :numbers, :through => :accounts_numbers, :extend => TimeDependent
end
Which allows me to do exactly what I want:
#numbers = #account.numbers.at(Date.new(2010,2,3));
Couldn't this be done by writing a function for Object?
class Object
def numbers_at(time)
start = time.to_date
end = time.advance(:days => 1).to_date
AccountNumber.join(:accounts, :numbers).where("time BETWEEN(?, ?)", start, end)
end
end

Resources