I'm trying to figure out how to write a method in my user model, that shows the user's full name and the title of the organisation that the user is from, in one line.
In my user model, I have a method called full_name, which works to add the first name and last name attributes together. That part works.
Now, I'm trying to write a method called 'formal_title', which adds the user's full name to an organisation.title.
I have models for User and Organisation. The associations are:
User
belongs_to :organisation
Organisation
has_many :users
My organisation table has an attribute called :title.
My attempt at writing a method in my user model is:
def full_name
if first_name.present?
[*first_name.capitalize, last_name.capitalize].join(" ")
else
test_full_name
end
end
def organisation_title
Organisation.title.where(organisation.id == self.organisation_id).titleize
end
def formal_title
[*self.full_name, self.organisation_title].join(",")
end
When I try this, I get an error in the console that says:
NoMethodError: undefined method `formal_title' for #<User:0x007fea3495fe90>
This error message makes no sense to me because the user that I'm testing this on has a first name, last name and organisation id to test against. the organisation_id in the user table references an organisation that has a title, so I'm lost as to why any part of this method could be missing but also confused about what the console doesn't know about the method.
Can anyone see what I've done wrong?
Entire user model has:
class User < ApplicationRecord
rolify strict: true # strict means you get true only on a role that you manually add
attr_accessor :current_role
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable,
:omniauthable, :omniauth_providers => [ :linkedin, :twitter, :orcid ]
# --------------- associations
belongs_to :organisation, optional: true
has_one :device
has_many :identities, dependent: :destroy
has_one :profile, dependent: :destroy
has_one :setting, dependent: :destroy
has_one :org_request, dependent: :destroy
has_many :proposals, dependent: :destroy
# Conversations
has_many :authored_conversations, class_name: 'Conversation', foreign_key: 'author_id'
has_many :received_conversations, class_name: 'Conversation', foreign_key: 'received_id'
has_many :personal_messages, dependent: :destroy
# End Conversations
# teams
has_many :teams, foreign_key: "team_mate_id"
has_many :team_projects, through: :teams, source: :proposal
has_many :received_invitations, :class_name => "TeamInvitation", :foreign_key => 'recipient_id'
has_many :sent_invitations, :class_name => "TeamInvitation", :foreign_key => 'sender_id'
# --------------- scopes
# --------------- validations
validates :first_name, presence: { message: 'First name cannot be blank.' }
validates :last_name, presence: { message: 'Last name cannot be blank.' }
validates :email, format: { with: EMAIL_REGEX }
validates :email, uniqueness: { message: 'This email is already associated with an account. Try logging in.'}
# validates_format_of :first_name, with: /a-zA-Z/
# validates_format_of :last_name, with: /a-zA-Z/
# validates_length_of :password, within: 8..128
# --------------- class methods
def full_name
if first_name.present?
[*first_name.capitalize, last_name.capitalize].join(" ")
else
"test_full_name"
end
end
def organisation_title
# you don't need to do `Organisation.where...` here because you have defined the `belongs_to` association for `:organisation`. So, it will directly give you the `Organisation` object.
organisation.title.titleize
end
def formal_title
[*self.full_name, self.organisation_title].join(",")
end
# --------------- instance methods
def online?
true
# false
# !Redis.new.get("user_#{self.id}_online").nil?
end
end
I save my changes each time. then I reload! in the console.
Not enough. You should either restart the console or re-query the user.
u = User.find(u.id)
reload! doesn't affect already loaded objects, so the user you had there, it won't see the new method.
Please try to change your code like this and let me know what it gives:
def full_name
if first_name.present?
[first_name.capitalize, last_name.capitalize].join(" ")
else
test_full_name # will work if test_full_name is defined inside this class
end
end
def organisation_title
# you don't need to do `Organisation.where...` here because you have defined the `belongs_to` association for `:organisation`. So, it will directly give you the `Organisation` object.
organisation.title.titleize
end
def formal_title
[self.full_name, self.organisation_title].join(",")
end
Related
I am building a rails app with devise and cancancan and I am trying to create associations within the same model. I have a User model, a role model and an appointments model. The user can have the role of Doctor or Patient. I want to create an association so a Patient can create appointments with the Doctor. I managed to create the associations but I do not know how to make the patient only create appointments with a doctor and only in that way.
My models are like this:
User model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
belongs_to :role, optional: true
validates :name, :DOB, presence: true
has_many :doctor_user_appointments, class_name: 'Appointment', foreign_key: 'doctor_user_id', dependent: :destroy
has_many :patient_user_appointments, class_name: 'Appointment', foreign_key: 'patient_user_id', dependent: :destroy
before_save :assign_role
scope :doctor_user, -> {where("role_id = ?", 1)
scope :patient_user, -> {where("role_id = ?", 2)
def admin?
role.name == 'Admin'
end
def doctor?
role.name == 'Doctor'
end
def patient?
role.name == 'Patient'
end
def assign_role
self.role = Role.find_by name: 'Patient' if role.nil?
end
end
Role model
class Role < ApplicationRecord
has_many :users, dependent: :restrict_with_exception
end
Appointment model
class Appointment < ApplicationRecord
belongs_to :doctor_user, class_name: 'User'
belongs_to :patient_user, class_name: 'User'
end
With these models, I can create appointments between doctor_user and patient_user, both users, but the associations do not distinguish who has the role "doctor" and who has the role "patient". I have tried with scopes but it is not working.
What I basically want is that doctor_user could only be a user with role "doctor" or user with role_id=1 and a patient_user only could be a user with role_id=2 or role "patient"
Any help would be welcome since I am quite stuck
Thanks in advance
Simply updating with the following lines to your Appointment controller will do the job:
belongs_to :doctor_user, -> { doctor_user }, class_name: 'User'
belongs_to :patient_user, -> { patient_user }, class_name: 'User'
I have fixed the issue by adding these lines to the Appointment model
belongs_to :doctor_user, -> {where("role_id = ?", 1)}, class_name: 'User'
belongs_to :patient_user, -> {where("role_id = ?", 2)}, class_name: 'User'
User model has_many Companies and Company belongs_to a User.
I now want to change this to a user has_one Company and Company has_one/belongs_to association. I have changed the models to look like this:
user.rb
class User < ApplicationRecord
has_one :company #was has_many
accepts_nested_attributes_for :company
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
company.rb
class Company < ApplicationRecord
belongs_to :user
has_many :employees, inverse_of: :company
has_many :quotes, inverse_of: :company
accepts_nested_attributes_for :employees, reject_if: :all_blank, allow_destroy: true #, :reject_if => lambda { |e| e.first_name.blank? }
accepts_nested_attributes_for :quotes, allow_destroy: true
validates_presence_of :user, :co_name, :co_number, :postcode
validates :co_name, length: { minimum: 2, message: "minimum of 2 chars" }
validates :co_number, format: { with: /\A([1-9]\d{6,7}|\d{6,7}|(SC|NI|AC|FC|GE|GN|GS|IC|IP|LP|NA|NF|NL|NO|NP|NR|NZ|OC|R|RC|SA|SF|SI|SL|SO|SP|SR|SZ|ZC|)\d{6,8})\z/,
message: "must be valid company number" }
validates :postcode, format: { with: /\A(?:gir(?: *0aa)?|[a-pr-uwyz](?:[a-hk-y]?[0-9]+|[0-9][a-hjkstuw]|[a-hk-y][0-9][abehmnprv-y])(?: *[0-9][abd-hjlnp-uw-z]{2})?)\z/,
message: "must be valid postcode" }
enum industry: [ :financial_services, :architect, :business_consultancy ]
end
and have rake db:reset and have changed this line in my Companies#create method:
#company = current_user.companies.new(company_params)
to
#company = current_user.company.new(company_params)
But I'm getting a
undefined method `new' for nil:NilClass
and I can't see where I'm going wrong. current_user should be available? the has_one association is defined so I should be able to call company.new on it right? I don;t need a foreign_key added to User, or do I?
Can anyone help me with where I'm going wrong? Thank you.
undefined method `new' for nil:NilClass
For has_one, you should use build_association method, so
#company = current_user.company.new(company_params)
should be
#company = current_user.build_company(company_params)
Here is a list of all available methods for has_one association
I've four user models: Zone, Product, User, Group
I want to choose what Users can sell a Product in a Zone, this is what Group does, with a many to many relation to User and a foreign key to one Product and one Zone. So I have one group per pair Zone/Product. I will also need to set custom attributes on that many to many relation so I used has_many :through Sell (I was unable to find a better name to describe the relation between Group and User).
So I ended up having 5 models: Zone, Product, User, Group, Sell.
It works fine, but now I'd need to select the next user available in a Group.
I was thinking to exploit Sell.id to find the user assigned to the same group with an higher id, if not present choose the first one again (this allows me to create a ring chain).
It would be useful to have a Group.next_user method.
Unfortunatly I can't figure out how to do this, I'd need help to find the next user available in the group (or the 1st one if there are no more users).
Follows the code for models all the models:
################
# models/group.rb
################
class Group < ActiveRecord::Base
has_many :sells
has_many :users, :through => :sells
belongs_to :zone
belongs_to :product
attr_accessible :priority, :product_id, :user_ids, :zone_id
end
################
# models/zone.rb
################
class Zone < ActiveRecord::Base
belongs_to :location
has_many :cities
has_many :groups
attr_accessible :name, :location_id
validates :location, :presence => true
end
################
# models/user.rb
################
class User < ActiveRecord::Base
after_create :create_calendar
before_destroy :destroy_calendar
belongs_to :location
belongs_to :mall
has_one :event_calendar
has_many :sells
has_many :groups, :through => :sells
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable, :registerable,
# :recoverable, :rememberable,
devise :database_authenticatable, :trackable,
:validatable, :authentication_keys => [:username]
# Setup accessible (or protected) attributes for your model
attr_accessible :username, :password, :password_confirmation, :remember_me, :name,
:surname, :role, :location_id
# attr_accessible :title, :body
ROLES = %w[Admin Agente Hostess HostessAdmin]
validates_uniqueness_of :username, :case_sensitive => false
validates :username, :presence => true
validates_presence_of :role, :name, :surname, :location_id
validates :location, :presence => true
validates :role, :inclusion => { :in => ROLES, :message => "%{value} non รจ un ruolo valido." }
def display_name
"#{self.name} #{self.surname}"
end
def has_role?(role)
# convert the role string to a sybmol
self.role.downcase.gsub(/\s+/, "_").to_sym == role
end
private
def create_calendar
if self.has_role? :agente
calendar = EventCalendar.new({:user_id => self.id})
calendar.save()
end
end
def destroy_calendar
if self.has_role? :agente
calendar = EventCalendar.find_by_user_id(self.id)
calendar.destroy()
end
end
def email_required?
false
end
def email_changed?
false
end
end
################
# models/product.rb
################
class Product < ActiveRecord::Base
after_create :create_groups
before_destroy :destroy_groups
attr_accessible :name
def create_groups
for zone in Zone.all
group = Group.new({:zone_id => zone.id, :product_id => self.id})
group.save()
end
end
def destroy_groups
for zone in Zone.all
group = Group.find_by_product_id(self.id)
group.destroy
end
end
end
################
# models/sell.rb
################
class Sell < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
Can you give me some help to get this done? Thanks!
If I get this right then addd this to your User model
scope :next, lambda { |p| {:conditions => ["id > ?", p.id], :limit => 1, :order => "id"} }
and this to your group model
def self.next_user
return User.first if Group.users.blank?
next_user = User.next(Group.users.last).first
return next_user || Group.users.first
end
This should do the trick. I didn't write test for this so you should test it :)
Right now, I have three models Post, Comment and User (using Devise) associated as follows:
post.rb:
class Post < ActiveRecord::Base
attr_accessible :title, :content, :total_votes
validates :title, :presence => true,
:length => { :maximum => 30 },
:uniqueness => true
validates :content, :presence => true,
:uniqueness => true
belongs_to :user
has_many :comments, :dependent => :destroy
end
comment.rb:
class Comment < ActiveRecord::Base
attr_accessible :content, :user_id
belongs_to :post, :counter_cache => true
belongs_to :user
end
user.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :username
validates_presence_of :username
has_many :posts, :dependent => :destroy
has_many :comments, :dependent => :destroy
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
if user = User.where(:email => data.email).first
user
else # Create a user with a stub password.
User.create!(:email => data.email, :password => Devise.friendly_token[0,20])
end
end
end
I want to add a fourth model called Vote with the following conditions:
Both posts and comments can be voted (up and down) and show the total/sum.
Each post will have many votes (up and down) and show the total/sum.
Each comment will have many votes
The ID of the user should be stored each time he or she votes so I can restrict one vote per user and show the ID/name of the users who voted (not sure where to store it)
Now, I'm not sure if this is a good occasion to use polymorphic associations and/or counter cache.
What's an efficient way of associating these Post, Comment, User and Voting models?
(If possible, I would like to see how the migration would look like)
This is a perfect textbook example of where a polymorphic association would be useful.
Your votes table migration would look like this:
create_table :votes do |t|
t.references :votable, :polymorphic => true
t.references :user
t.integer :polarity
t.integer :total
end
This would create a table with this schema:
id INTEGER
votable_id INTEGER
votable_type VARCHAR
user_id INTEGER
polarity INTEGER
total INTEGER
Here, user_id would be the person who cast the vote, polarity would be either '1' for an upvote or '-1' for a downvote (this lets you just sum the polarities to get upvotes and downvotes to cancel), votable_type would contain what the vote is for (Post or Comment), votable_id would contain the id of the thing the vote is for, and total would keep a running total of the vote sum (for efficiency).
Then your models would look like this:
class Vote < ActiveRecord::Base
belongs_to :votable, :polymorphic => true
belongs_to :user
before_create :update_total
protected
def update_total
self.total ||= 0
self.total += self.polarity
end
end
class Post < ActiveRecord::Base
has_many :votes, :as => :votable
end
class Comment < ActiveRecord::Base
has_many :votes, :as => :votable
end
class User < ActiveRecord::Base
has_many :votes
end
This is a bit complicated and I'm not sure how to implement it. I have a User model and a Relationship model. Users are able to "follow" each other (just like twitter). The relationship model is all setup properly and works great.
Next, I have an Event model. Each user has_and_belongs_to_many events (many to many association between users and events). Users "attend" events.
What I would like to do is pull a list of all events that are
being attended by the current_user
are being attended by users that current_user is following.
If possible, I would like to have this list accessible via the User model so I can say current_user.event_feed and it will list all events as mentioned above.
Here are my models:
class Event < ActiveRecord::Base
attr_accessible :name,
:description,
:event_date,
:location,
:owner_id,
:category,
:photo
CATEGORIES = ['Music', 'Outdoors', 'Party']
has_and_belongs_to_many :users
and relationship model:
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
belongs_to :follower, :class_name => "User"
belongs_to :followed, :class_name => "User"
validates :follower_id, :presence => true
validates :followed_id, :presence => true
end
and user model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation, :time_zone
has_and_belongs_to_many :events
has_many :relationships, :dependent => :destroy,
:foreign_key => "follower_id"
has_many :reverse_relationships, :dependent => :destroy,
:foreign_key => "followed_id",
:class_name => "Relationship"
has_many :following, :through => :relationships,
:source => :followed
has_many :followers, :through => :reverse_relationships,
:source => :follower
Thanks!
This is rails 3 only, but quite elegant (untested, hopefully my memory of habtm relationships is ok).
class User < ActiveRecord::Base
# ...
def event_feed
ids = self.followers.collect(&:id) << self.id
Event.includes(:users).where(["`users`.id IN (#{ids.join(',')})"])
end
# ...
end
Event Model:
scope :attended, where("event_date < #{Date.today}")
User Model:
# Returns collection of events that are attended by user and users she follows
def attended events
attended_events = []
attended_events << events.attended
followers.each do |follower|
attended_events << follower.events.attended
end
attended_events
end
1) being attended by the current_user and
This can be achieved simply by calling current_user.events
2) are being attended by users that current_user is following.
This is a little trickier. You want to end up with a flattened list of other user's events: current_user.following.collect { |friend| friend.events }.flatten #=> returns an array of followers' events
Since you want to display all events in a single list (from what I could gather), I think a presenter class would be useful:
class EventFeed
attr_accessor :event, :display_name
def initialize(event, name)
self.event = event
self.name = name
end
end
And now, adding them together to get to current_user.event_feed
class User
def event_feed; []; end
end
And gluing it all together:
current_user.events.each { |e| current_user.event_feed << EventFeed.new(e, 'YOU') }
current_user.following.each do |friend|
friend.events.each { |e| current_user.event_feed << EventFeed.new(e, friend.name) }
end
current_user.event_feed #=> an array of EventFeed objects where you can display "You are going to #{event.name}"
Of course this is pseudo code, but it should get you on the right track