Naming error in controller - ruby-on-rails

I get this error when trying to create a record in my joins table
NameError in
SubscriptionsController#new
uninitialized constant
Channel::ChannelsUser
Subscriptions Controller
class SubscriptionsController < ApplicationController
helper_method :current_user_session, :current_user
filter_parameter_logging :password, :password_confirmation
def new
#channel = Channel.find(params[:channel_id])
#user = current_user
#channel.subscribers << #user
#channel.save
flash[:notice] = "You have subscribed to: " +#channel.name
redirect_to #channel
end
end
end
User Model
class User < ActiveRecord::Base
acts_as_authentic
ROLES = %w[admin moderator subscriber]
#Each user can subscribe to many channels
has_many :channels_users
has_many :subscriptions, :class_name => "Channel", :through => :channels_users
#Each user who is a moderator can moderate many channels
has_many :channel_mods
has_many :channels, :through => :channel_mods
#Each user can receive many messages
has_many :messages_users , :dependent => :destroy
has_many :reciepts , :class_name => "User", :through => :messages_users
#Filter users by role(s)
named_scope :with_role, lambda { |role| {:conditions => "roles_mask & #{2**ROLES.index(role.to_s)} > 0 "} }
def roles
ROLES.reject { |r| ((roles_mask || 0) & 2**ROLES.index(r)).zero? }
end
def roles=(roles)
self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.sum
end
def role_symbols
role.map do |role|
role.name.underscore.to_sym
end
end
end
Channel Model
class Channel < ActiveRecord::Base
#Each channel owns many or no messages
has_many :messages
#Each channel is own by one moderator
has_many :channel_mods
has_many :moderators, :class_name =>'User', :through =>:channel_mod
#Each channel can have and belong to many or no users
has_many :channels_users
has_many :subscribers, :class_name => 'Users' , :through => :channels_users
end
ChannelsUsers model
class ChannelsUsers < ActiveRecord::Base
belongs_to :user
belongs_to :channel
end

This would read much nicer if you change the model to ChannelUser. Here are the corresponding relationships:
class Channel < ActiveRecord::Base
has_many :channel_users
has_many :users, :through => :channel_users
end
class User < ActiveRecord::Base
has_many :channel_users
has_many :channels, :through => :channel_users
end
class ChannelUser < ActiveRecord::Base
belongs_to :channel
belongs_to :user
end
Your join table would then be called channel_users. I think you named it channels_users initially because that's the setup for a has_and_belongs_to_many join table. But since you're using has_many :through, you're free to name the table as you like.
I wrote a blog article earlier this year that walks through all the options in detail:
Basic Many-to-Many Associations in Rails
I hope this helps!

Your channel user class name is a plural. It is supposed to be singular.
So either you can change to this:
class ChannelsUser < ActiveRecord::Base
belongs_to :user
belongs_to :channel
end
or change this line in User and Channel model:
has_many :channels_users
to
has_many :channels_users, :class_name => 'ChannelsUsers'
Rails will use the methods like String#classify and String#underscore to detect classes and relationships.
If you want to play around with the names, in the console try out various combinations:
>> "channels_users".classify
=> "ChannelsUser"
>> "ChannelsUser".underscore
=> "channels_user"

Related

Rails - Restrict users from following themselves

I have a following system and I would like to restrict the users controller action 'follow' if the params[:id] is the same as the current user.
I use cancancan (an up to date cancan gem) to do my authorizations work.
controllers/users_controller.rb
def follow
Followership.create(leader_id: params[:id], follower_id: current_user.id)
...
end
models/user.rb
class User < ActiveRecord::Base
has_many :followers, :class_name => 'Followership', dependent: :destroy
has_many :followed_by, :class_name => 'Followership', dependent: :destroy
...
end
models/followership.rb
class Followership < ActiveRecord::Base
belongs_to :leader, :class_name => 'User'
belongs_to :follower, :class_name => 'User'
...
end
Add a validation on your Followship model:
class Followership < ActiveRecord::Base
belongs_to :leader, :class_name => 'User'
belongs_to :follower, :class_name => 'User'
validate :doesnt_follow_self
private
def doesnt_follow_self
errors.add(:base, 'You can\'t follow yourself') if leader == follower
end
end
Perhaps you can use a validation:
#app/models/followership.rb
Class FollowerShip < ActiveRecord::Base
include ActiveModel::Validations
...
validates_with FollowValidator
end
#app/validators/follow_validator.rb
class FollowValidator < ActiveModel::Validator
def validate(record)
if record.leader_id == record.follower_id
record.errors[:leader_id] << "Sorry, you can't follow yourself!"
end
end
end
I was half-way through writing this when #BroiStatse posted

Updating child model with same data as parent

I have four models related to each other as below:
class User < ActiveRecord::Base
has_many :clients
has_many :default_prices
end
class DefaultPrice < ActiveRecord::Base
has_many :client_prices
has_many :clients, :through => :user
belongs_to :user
end
class Client < ActiveRecord::Base
belongs_to :user
has_many :client_prices
before_create do
user.default_prices.each do |default_price|
client_prices.build("price" => default_price.price, "visit_type" => default_price.visit_type, "default_price_id" => default_price.id)
end
end
end
class ClientPrice < ActiveRecord::Base
belongs_to :client
belongs_to :default_price
end
Right now when a new client is created by the user, the user's default prices are applied to the client's client_prices table. How can I have new client_prices (for each existing client) created when new default_prices are created by the user? Also, how can I have the client_prices update when the default prices are changed? Each client prices has an default_price_id column that relates to the default price, if that helps.
class DefaultPrice < ActiveRecord::Base
before_create :new_client_price
before_update :update_clients_price
private
def new_client_price
clients.each do |c|
self.client_prices.create(:price => self.price, :visit_type => self.visit_type, :client_id => c.id)
end
end
def update_clients_price
self.client_prices.each do |p|
p.price = self.price
p.visit_type = self.visit_type
p.save
end
end

Rails has_many through with condition, build new

I've got users and organisations with a join model UsersOrganisation. Users may be admins of Organisations - if so the is_admin boolean is true.
If I set the is_admin boolean by hand in the database, Organisations.admins works as I'd expect.
In the console, I can do Organisation.first.users << User.first and it creates an organisations_users entry as I'd expect.
However if I do Organisation.first.admins << User.last it creates a normal user, not an admin, ie the is_admin boolean on the join table is not set correctly.
Is there a good way of doing this other than creating entries in the join table directly?
class User < ActiveRecord::Base
has_many :organisations_users
has_many :organisations, :through => :organisations_users
end
class Organisation < ActiveRecord::Base
has_many :organisations_users
has_many :users, :through => :organisations_users
has_many :admins, :through => :organisations_users, :class_name => "User",
:source => :user,
:conditions => {:organisations_users => {:is_admin => true}}
end
class OrganisationsUser < ActiveRecord::Base
belongs_to :organisation
belongs_to :user
end
You can always override the << method of the association:
has_many :admins do
def <<(user)
user.is_admin = true
self << user
end
end
(Code has not been checked)
there are some twists with the has_many :through and the << operator. But you could overload it like in #Erez answer.
My approach to this is using scopes (I renamed OrganisationsUsers to Memberships):
class User < ActiveRecord::Base
has_many :memberships
has_many :organisations, :through => :memberships
end
class Organisation < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships, :class_name => 'User', :source => :user
# response to comment:
def admins
memberships.admin
end
end
class Memberships < ActiveRecord::Base
belongs_to :organisation
belongs_to :user
scope :admin, where(:is_admin => true)
end
Now I create new admins like this:
Organisation.first.memberships.admin.create(:user => User.first)
What I like about the scopes is that you define the "kind of memberships" in the membership class, and the organisation itself doesn't have to care about the kinds of memberships at all.
Update:
Now you can do
Organisation.first.admins.create(:user => User.first)
You can try below code for organization model.
class Organisation < ActiveRecord::Base
has_many :organisations_users
has_many :organisations_admins, :class_name => "OrganisationsUser", :conditions => { :is_admin => true }
has_many :users, :through => :organisations_users
has_many :admins, :through => :organisations_admins, :source => :user
end

Many to many relationship , rails

I have a many-to-many relation between users and the channels they subscribe to. But when I look at my model dependency between user and user channels or channels and users channel instead there is a direct connection between users and channels. how do i put Users channels between to two?
User Model
class User < ActiveRecord::Base
acts_as_authentic
ROLES = %w[admin moderator subscriber]
has_and_belongs_to_many :channels
has_many :channel_mods
named_scope :with_role, lambda { |role| {:conditions => "roles_mask & #{2**ROLES.index(role.to_s)} > 0 "} }
def roles
ROLES.reject { |r| ((roles_mask || 0) & 2**ROLES.index(r)).zero? }
end
def roles=(roles)
self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.sum
end
def role_symbols
role.map do |role|
role.name.underscore.to_sym
end
end
end
Channel Model
class Channel < ActiveRecord::Base
acts_as_taggable
acts_as_taggable_on :tags
has_many :messages
has_many :channel_mods
has_and_belongs_to_many :users
end
UsersChannel Model
class UsersChannels < ActiveRecord::Base
end
See the has_many :through documentation on the Rails guides which guides you through configuring a has_many relationship with an intervening model.
The HABTM relationship generates the UsersChannels auto-magically - if you want to access the model for the link table (add some more attributes to it for example - time_channel_watched or whatever), you'll have to change the models (and explicitly define and migrate a UsersChannel model with the attributes id:primary_key, user_id:integer, channel_id:integer) to :
class Channel < ActiveRecord::Base
has_many :users_channels, :dependent => :destroy
has_many :users, :through => :users_channels
end
class User < ActiveRecord::Base
has_many :users_channels, :dependent => :destroy
has_many :channels, :through => :users_channels
end
class UsersChannels < ActiveRecord::Base
belongs_to :user
belongs_to :channel
end
Note: since you're defining you're own link model you don't have to stay with the HABTM defined table name of UsersChannels - you could change the model name to something like "Watches". All of the above is pretty much in the Rails guide that has been mentioned.

Retrieving unique associated models from an array of another model

What is the recommended approach for finding multiple, unique associated models for a subset of another model? As an example, for a subset of users, determine unique artist models they have favorited.
One approach is to grab the users from the database, then iterate them all quering for favorites and building a unique array, but this seems rather inefficient and slow.
class User < ActiveRecord::Base
has_many :favorites
end
class Artist < ActiveRecord::Base
has_many :favorites
end
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :artist
end
#users = User.find_by_age(26)
# then determine unique favorited artists for this subset of users.
The has_many association has a option called uniq for this requirement:
class User < ActiveRecord::Base
has_many :favorites
has_many :artists, :through => :favorites, :uniq => true
end
class Artist < ActiveRecord::Base
has_many :favorites
has_many :users, :through => :favorites, :uniq => true
end
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :artist
end
Usage:
# if you are expecting an array of users, then use find_all instead of find_
#users = User.find_all_by_age(26, :include => :artists)
#users.each do |user|
user.artists # unique artists
end
Edit 1
I have updated the answer based on user's comment.
Solution 1- :group
Artist.all(:joins => :users, :group => :id,
:conditions => ["users.age = ?", 26])
Solution 2- SELECT DISTINCT
Artist.all(:joins => :users, :select => "DISTINCT artists.*",
:conditions => ["users.age = ?", 26]))

Resources