Has_and_belongs_to same model - ruby-on-rails

I have and User model that will need to 'own' itself. Thats because the only way to a user connect with another is if they have this 'bound'.
So... I created a 'join model':
class CreateUserConnections < ActiveRecord::Migration
def change
create_table :user_connections, id: false do |t|
t.string :token
t.integer :user_a_id, :null => false
t.integer :user_b_id, :null => false
t.timestamps
end
add_index :user_connections, :token, unique: true
add_index :user_connections, [:user_a_id, :user_b_id], unique: true
end
end
and
class UserConnection < ActiveRecord::Base
belongs_to :user
belongs_to :user_a, :class_name => 'User'
belongs_to :user_b, :class_name => 'User'
before_create :generate_token
protected
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless UserConnection.exists?(token: random_token)
end
end
end
and then created the relation on my user model:
#unlocked users
has_and_belongs_to_many :users,
:join_table => "user_connections",
:foreign_key => "user_a_id",
:association_foreign_key => "user_b_id"
The problem is that when I create a relation with a user like:
User.find(1).users << User.find(2)
It creates the own relation from User 1 to User 2, but I tought that with the many_to_many relation the ownership relation from user 2 to 1 would be automatic.
What I'm missing here?
Thanx in advance

I should create the reverse realationship by myself.

Related

How to call database attributes in models in Ruby on Rails

I am making a project with ruby on rails, i have recipes table in the db recipes table has user_id etc. Also i have a user table and in this table i have an attribute called n_days. I am using sidekiq to do some background processes (for automatically delete in n days) In models(recipe.rb) i need to reach user_id(in recipe table) and n_days(in user table) but i do not know how to access them.
i tried this code but i get NoMethodError
scope :recent, -> { where('created_at <= :a', a: Time.now - User.find_by(id: :user_id.to_s.to_i).n_days.days) }
I think it would be simplier to set deleted_at datetime field in your sidekiq worker, it will make easier to compare datetimes. However, if you want to use n_days integer column, you can write such code:
class Recipe < ApplicationRecord
belongs_to :user
validates :name, :text, presence: true
# scope :recent,
# -> {
# joins(:user)
# .where.not(users: { deleted_at: nil })
# .where('recipes.created_at <= users.deleted_at')
# }
# For SQLite database
scope :recent,
-> {
joins(:user)
.where("recipes.created_at <= datetime('now', '-' || ifnull(users.n_days, 0) || ' days')")
}
end
class User < ApplicationRecord
has_many :recipes, dependent: :destroy
validates :first_name, :last_name, presence: true
end
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.integer :n_days
t.datetime :deleted_at
t.timestamps
end
end
end
class CreateRecipes < ActiveRecord::Migration[5.2]
def change
create_table :recipes do |t|
t.string :name
t.text :text
t.references :user, foreign_key: true
t.timestamps
end
end
end
# seeds.rb
User.destroy_all
10.times do |t|
r = rand(15)
u = User.create!(
first_name: SecureRandom.uuid,
last_name: SecureRandom.uuid,
n_days: rand(15),
deleted_at: Time.now - r.days
)
5.times do |t|
u.recipes.create!(
name: SecureRandom.uuid,
text: SecureRandom.uuid,
created_at: 60.days.ago
)
end
end
p Recipe.recent.inspect

NoMethodError (undefined method `primary_key' for User

I'm using the Public Activity gem to create a activity stream on my app. For that I have this setup.
I have set up a friendship model for my app,
create_table "friendships", force: :cascade do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
And the model looks like this,
belongs_to :user
belongs_to :friend, :class_name => "User"
And this is my user model,
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
And I have tracked my movie model with the Public Activity gem,
include PublicActivity::Model
tracked owner: -> (controller, model) { controller && controller.current_user }
tracked recipient: -> (controller, model) { controller && controller.current_user.friends }
tracked :params => {
:title => :get_title,
:poster => :get_poster
}
has_and_belongs_to_many :users
When I add a new movie, and thus create a new activity, I get this error in my rails console,
NoMethodError (undefined method primary_key' for User::ActiveRecord_Associations_CollectionProxy:Class):
app/controllers/movies_controller.rb:19:increate'
And that refers to this create def in my movies_controller,
def create
#movie = Movie.find_or_create_by movie_params
current_user.movies << #movie
redirect_to :root
end
What's going wrong here?
* as requested the movie parameters *
private
def movie_params
params.require(:movie).permit(
:title,
:image,
:imdb_rating,
:release_date,
:movie_id,
:backdrop
)
end
Okay, found it! Your problem is here:
tracked recipient: -> (controller, model) { controller && controller.current_user.friends }
I think tracked method asks for a model record(or nil), but your controller.current_user.friends return an association, so it does not have primary_key. You could change it to controller.current_user.friends.try(:first) to verify.

nested form not displaying in Rails

I have a form for a model called isp, which 'has_many' isp accounts. the isp account belongs to to 'isp'.
There is a validation on the isp_account that means it cant be added if there isnt an isp_id, so my reasoning is to created a nested form. I created the nested form like so
= simple_form_for #isp, :html => { :multipart => true } do |f|
= f.input :title
= f.simple_fields_for :isp_accounts do |tag|
= tag.input :title, label: "Tag Name"
however the nested attribute isnt being displayed. There are no errors etc. Why is this? Am I approaching this in the best way? is this the only way?
here's the code
ISP MODEL
class Isp < ActiveRecord::Base
has_many :isp_accounts, dependent: :destroy
has_many :deployments, through: :servers
has_many :servers, through: :isp_accounts
validates :title, presence: true
accepts_nested_attributes_for :isp_accounts
end
ISP ACCOUNTS MODEL
class IspAccount < ActiveRecord::Base
belongs_to :isp
has_many :deployments, through: :servers
has_many :servers, dependent: :destroy
validates :title, presence: true
validate :check_associates
private
def check_associates
associated_object_exists Isp, :isp_id
end
end
ISP ACCOUNT CONTROLLER
....
def new
#isp_account = IspAccount.new
end
def update
#isp_account.update_attributes(isp_accounts_path)
if #isp_account.save
record_saved
return redirect_to(isp_accounts_path)
else
check_for_errors
return render('/isp_accounts/edit')
end
end
private
def get_isp_accounts
#isp_account = IspAccount.all
end
def get_isp_account
#isp_account = IspAccount.find(params_isp_accounts)
end
def params_isp_accounts
params.require(:isp_account).permit!
end
end
....
def new
#isp = Isp.new
end
def update
#isp.update_attributes(params_isp)
if #isp.save
record_saved
return redirect_to(isps_path)
else
check_for_errors
return render('new')
end
end
private
def params_isp
params.require(:isp).permit(:title, isp_accounts_attributes: [:id, :title])
end
def get_isp
#isp = Isp.where(id: params[:id]).first
unless #isp
record_not_found
return redirect_to(isps_path)
end
end
def get_isps
#isp = Isp.all.order(:title)
end
end
SCHEMA
create_table "isp_accounts", force: true do |t|
t.string "title"
t.integer "isp_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "isps", force: true do |t|
t.string "title"
t.datetime "created_at"
t.datetime "updated_at"
end
ok i got it. I was missing the new bit for that attribute in my controller. pretty basic really.
def new
#isp = Isp.new
#isp.isp_accounts.new
end

attr_encrypted nil after saving object

My seeds.rb:
doc = Document.new(
:name => "Document"+i,
:description => "Description of Document"+i,
:content => "hello world",
:public => false
)
doc.save! #encrypted_content and content will be nil after this line
doc.content = "hello"
doc.save! #encrypted_content will be set correctly after this line
My Document model:
class Document < ActiveRecord::Base
include KeyCreator
has_many :document_users, dependent: :destroy
has_many :users, :through => :document_users
before_create :before_create
validates :name, presence: true
validates :description, presence: true
attr_encrypted :content, key: 'secret'
def self.admin_in(user)
Document.joins(:document_users)
.where(:document_users => {:user => user, :role => ["owner", "admin"]})
.order(:name)
end
def self.non_admin_in(user)
Document.joins(:document_users)
.where(:document_users => {:user => user})
.where.not(:document_users => {:role => ["owner", "admin"]})
.where.not(:document_users => {invite_accepted_date: nil})
.order(:name)
end
def current_doc_user(current_user)
self.document_users.find_by(user: current_user)
end
def to_param
"#{link_key}".parameterize
end
def after_create(current_user)
#setting email to avoid validation error
self.document_users.create!(user: current_user, email: current_user.email, role: "owner")
end
private
def before_create
now = DateTime.now
self.content = nil
self.api_key = create_key
self.link_key = create_key
end
end
My migration:
class CreateDocuments < ActiveRecord::Migration
def change
create_table :documents do |t|
t.string :name
t.text :description
t.text :encrypted_content #I also tried t.string. It doesn't work either
t.boolean :public
t.string :api_key
t.string :link_key
t.timestamps
end
add_index :documents, :api_key, unique: true
add_index :documents, :link_key, unique: true
#add_index :users, :reset_password_token, unique: true
end
end
My environment is rails 4.0.3, ruby 2.1.1.
Setting content on new and also create doesn't seem to work. It works when I set the field and then save again. There are no raised errors.

Rails Beginner Attempting TDD:

I'm new to unit testing and Rails in general. I've decided to build my projects in a TDD environment, but this has left me with some early questions.
I need help building the models to pass this test:
describe User do
it "should add user to team" do
team = Team.create(:name => "Tigers")
akash = User.create(:name => "Akash")
akash.teams << team
akash.memberships.size.should == 1
end
it "should allow buddyup"
john = User.create(:name => "John")
john.buddyup_with(akash)
john.memberships.size.should == 1
end
it "should validate linked buddys"
akash.buddys.should include(john)
end
end
Basically, ALL I want to do right now is pass the tests. Here is what I have so far:
class Team < ActiveRecord::Base
has_and_belongs_to_many :users
attr_accessubke :name
validates :name, :presence = true
:uniqueness => true
end
class User < ActiveRecord::Base
has_and_belongs_to_many: :teams
attr_accessible :name
validates :name, :presence = true
:uniqueness => true
end
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :name
t.timestamps
end
end
def self.down
drop_table :users
end
end
class CreateTeams < ActiveRecord::Migration
def self.up
create_table :teams do |t|
t.string :name
t.timestamps
end
end
def self.down
drop_table :teams
end
end
class CreateTeamsUsersJoinTable < ActiveRecord::Migration
def self.up
create_table :teams_users, :id => false do |t|
t.integer :team_id
t.integer :user_id
end
end
def self.down
drop_table :teams_users
end
end
That is all I have so far, and clearly it is nowhere near completion. Could you provide some insight, and perhaps code I should use to complete this? My biggest problem right now is the buddyup_with part. Adding a buddy will add a person to every team you are a member of, think of teams as parts of a development company, and buddys as understudies or something.
Suggestions I would make:
Use before do
# code #
end
to set up your conditions.
Do 1 test per. You have a lot going on there :)
Use Factory Girl.
Try what you have and work from there (Agile approach, even to adding tests).

Resources