I have a Lead model
# A Lead is a type of User that might be interested in using our service.
class Lead < User
validates :first_name, presence: true
has_many :notes, dependent: :destroy
def self.search(search)
...
end
end
A Lead inherits from a User
class User < ActiveRecord::Base
has_one :location, dependent: :destroy
accepts_nested_attributes_for :location
delegate :address, :city, :state, :zip_code,
:full_location, :latitude, :longitude,
to: :location, allow_nil: true
end
All Users have one Location, which includes data like :address, :city, :state, etc.
I have this controller
class LeadsController < ApplicationController
helper_method :sort_column
def index
#leads = Lead.search(params[:search])
.joins(:location)
.order("#{sort_column} #{sort_direction}")
.page(params[:page])
.per(8)
end
end
I have this test
describe LeadsController, type: :controller do
before do
#lead = create :lead
end
describe 'GET #index' do
it 'populates an array of leads' do
get :index
assigns(:leads).should eq [#lead]
end
end
The spec fails and says #leads is empty.
The spec passes when I delete the line .joins(:location) in the LeadsController
Everything works fine in development. The app is able to pull up all the correct data and display it.
For some reason, .joins in the test environment causes #leads to be empty.
I need that line. I need it to be able to sort Leads(Users) by their zip_code. Remember, zip_code is stored in a Location object.
My question is: How do I get my spec passing while keeping sortability for zip codes? What's happening differently in the test environment?
Thanks!
It appears your test sets up a Lead without a Location. Note that joins performs an INNER JOIN, so it's going to pull all the leads that have an associated location. Leads without a location will not be returned.
This is a nice example from the Rails docs:
User.joins(:posts)
=> SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
You can do:
before do
#lead = create(:lead)
create(:location, user: #lead)
end
and your test should pass.
Related
Terribly worded, but I'm confusing it.
I have a User model who has_many Clients and has_many statements, through: :clients and then statements which belongs_to clients and belongs to user
In Console I can do all the queries I want. User.statements User.client.first.statements etc - What I'm struggling on is Controller restrictions
For now it's simple - A user should only be able to see Clients and Statements in which they own.
For Clients I did
Client Controller
def index
#clients = Client.where(user_id: current_user.id)
end
Which seems to work perfectly. Client has a field for user_id
I'm kind of stuck on how to emulate this for Statements. Statements do -not- have a user_id field. I'm not quite sure I want them too since in the very-soon-future I want clients to belongs_to_many :users and Statements to not be bound.
Statement Controller
def index
#clients = Client.where(user_id: current_user.id)
#statements = Statement.where(params[:client_id])
end
I'm just genuinely not sure what to put - I know the params[:client_id] doesn't make sense, but what is the proper way to fulfill this? Am I going about it an unsecure way?
Client Model
class Client < ApplicationRecord
has_many :statements
has_many :client_notes, inverse_of: :client
belongs_to :user
validates :name, presence: true
validates :status, presence: true
accepts_nested_attributes_for :client_notes, reject_if: :all_blank, allow_destroy: true
end
Statement Model
class Statement < ApplicationRecord
belongs_to :client
belongs_to :user
validates :name, presence: true
validates :statement_type, presence: true
validates :client_id, presence: true
validates :start_date, presence: true
validates :end_date, presence: true
end
User Model
class User < ApplicationRecord
has_many :clients
has_many :statements, through: :clients
end
Based on the reply provided below I am using
def index
if params[:client][:user_id] == #current_user.id
#clients = Client.includes(:statements).where(user_id: params[:client][:user_id])
#statements = #clients.statements
else
return 'error'
end
end
Unsure if this logic is proper
Use includes to avoid [N+1] queries.
And regarding "A user should only be able to see Clients and Statements in which they own".
if params[:client][:user_id] == #current_user.id
#clients = Client.includes(:statements).where(user_id: params[:client][:user_id])
# do more
else
# Type your error message
end
Additionally, you might need to use strong params and scope.
The best way to do it is using includes:
#clients = Client.where(user_id: current_user.id)
#statements = Statement.includes(clients: :users}).where('users.id = ?', current_user.id)
You can take a look in here: https://apidock.com/rails/ActiveRecord/QueryMethods/includes
In this case, thanks to the reminder that current_user is a helper from Devise, and the relational structure I showed, it was actually just as simple as
def index
#statements = current_user.statements
end
resolved my issue.
Due to the [N+1] Queries issue that #BigB has brought to my attention, while this method works, I wouldn't suggest it for a sizable transaction.
I am building a very simple application managing users and keys. Keys are nested attributes of a user. I am inspired by RailsCast #196.
The models are defined as:
class User < ApplicationRecord
#Validations
---
#Relations
has_many :keys, :dependent => :destroy
accepts_nested_attributes_for :keys, :reject_if => :all_blank, :allow_destroy => true
end
class Key < ApplicationRecord
#Validations
---
#Relations
belongs_to :user
end
The users controller includes strong parameters:
def user_params
params.require(:user).permit(:nom, :prenom, :section, :email, :password, keys_attributes: [:id, :secteur, :clef])
end
And I wish to initialize 1 key for each new user (users controller):
# GET /users/new
def new
#user = User.new
key = #user.keys.build({secteur: "Tous", clef: "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678"})
end
I tried many ways to initialize the user's key, but I can't figure out how to pass parameters to it. User is always created, but the first key is not. No error message is issued.
Remove the '{' tags inside the build method parameters. Should be:
key = #user.keys.new(secteur: "Tous", clef: "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678")
Also, the build method is just an alias for 'new' and used to behave differently on rails 3 apps so I've always strayed away from it and just use the more familiar 'new' method. Obviously don't forget to then save the object at some point in your controller.
I have notices and comments on those notices. The comments are also of class notice. When a notice is submitted for creation, its commentee_id will be blank if it is an original notice. If the notice is a comment on another notice, its commentee_id will be the id of the notice it is commenting on.
notices_controller.rb:
def create
#character = Character.find_by(callsign: params[:callsign])
#notice = #character.notices.build(notice_params)
if #notice.save
.
.
end
end
def notice_params
params.require(:notice).permit( :content, :picture, :latitude, :longitude,
active_comment_relationship_attributes: [:commentee_id] )
end
notice.rb:
belongs_to :character
has_one :active_comment_relationship, class_name: "Commentrelationship",
foreign_key: "commenter_id",
dependent: :destroy
has_one :supernotice, through: :active_comment_relationship, source: :commentee
accepts_nested_attributes_for :active_comment_relationship
before_validation :create_commentee
private
def create_commentee
if !commentee_id.blank?
create_active_comment_relationship(commentee_id: :commentee_id)
end
end
The new notice model doesn't get successfully created. I get the error message:
undefined local variable or method `commentee_id' for #<Notice:0x0000010c3a4708>)
When create_commentee is called, the new #notice instance has been created in memory but not yet saved to the database (create_commentee is a before_validation callback). At this stage, how do you correctly access commentee_id?
Neither this (!commentee_id.blank?):
def create_commentee
if !commentee_id.blank?
create_active_comment_relationship(commentee_id: :commentee_id)
end
end
nor this (commentee_id not :commentee_id):
def create_commentee
if !commentee_id.blank?
create_active_comment_relationship(commentee_id: commentee_id)
end
end
makes any difference.
At least one problem is that you're doing this:
if !:commentee_id.blank?
...when you should be doing this:
if !commentee_id.blank?
:commentee_id is a symbol, which is like a special kind of string, and it will never be blank (well, unless it's :""). You want to call the commentee_id method, i.e. without :).
P.S. If you want to be slightly more idiomatic, I'd recommend this instead:
if commentee_id.present?
I want to order posts based on the total votes it has. This is what I have in the Post Model:
class Post < ActiveRecord::Base
attr_accessible :title, :url
validates :title, presence: true
validates :url, presence: true
has_many :votes
def vote_number
votes.where(direction: "up").count - votes.where(direction: "down").count
end
end
And this is what I attempted to do in the Post Controller:
def index
#posts = Post.last(10).order('vote_number')
end
Nevertheless I get this error from the index:
undefined method `order' for #<Array:0x3787158>
The other questions in Stack Overflow resolved this problem by making the calculation in the Post Controller but I can not do it because votes are arrays and not integers.
Found a way to solve it. Instead of using order I used sort_by.
Instead of having this in the Post Controller:
def index
#posts = Post.last(10).order('vote_number')
end
I used sort_by:
def index
#posts = Post.all.sort_by{|post|-post.vote_number}
end
You should try counter cache.
You can read more about it from the following links -
How to sort authors by their book count with ActiveRecord?
http://hiteshrawal.blogspot.com/2011/12/rails-counter-cache.html
http://railscasts.com/episodes/23-counter-cache-column
Counter cache only works inside rails. If you updated from outside application you might have to do some work around.
first, last and all execute query. Insert order always before those three.
class Post < ActiveRecord::Base
attr_accessible :title, :url
attr_reader :vote_difference # getter
attr_writer :vote_difference # setter
validates :title, presence: true
validates :url, presence: true
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :post, :counter_cache => true
#more class methods here
def after_save
self.update_counter_cache
end
def after_destroy
self.update_counter_cache
end
def update_counter_cache
post.vote_difference = post.comments.where(direction: 'up').count - post.comments.where(direction: 'down').count
post.save
end
end
Now you can sort by vote_difference when you query up.
for example -
posts = Post.order(:vote_difference, :desc)
I haven't check the correctness of my code yes. If you find any issues please let me know. I am sure it can be adapted to make it works.
If you follow this pattern to use counter_cache you might will to run a migration to add a vote_difference column, and another migration to update the vote_difference column for previous created post.
If I have the following code:
class User < ActiveRecord::Base
has_many :problems
attr_accessible :email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :location, :address
attr_internal_accessor :user_address
def user_address
self.address
end
end
class Problem < ActiveRecord::Base
belongs_to :user
validates_presence_of :user_id, :title, :description, :tags
delegate :address, :to => :user, :prefix => true
end
When I try to do this in AR this is what the call looks like:
Problem Load (0.2ms) SELECT "problems".* FROM "problems" ORDER BY problems.user_address asc LIMIT 20 OFFSET 0
SQLite3::SQLException: no such column: problems.user_address: SELECT "problems".* FROM "problems" ORDER BY problems.user_address asc LIMIT 20 OFFSET 0
Completed 500 Internal Server Error in 173ms
It gives me an error that it is not a column which is true, however it generates data just like active record would.
How can I search the output if this function as if it was a native active record column?
The way i usually do this is by using the model you want to return.
So if its addresses you want, something like:
def user_address
Address.joins(:users).where(:user_id => user.id)
end
This way you get an AR relation object back and you can chain them.
The method user_address is supposed to be used in the code (mostly views), not to be passed to AR.
AR would require things to be more understood by the DB.
To DB sort (order) using the User#address column:
#Rails 3
p = Problem.includes(:user).order("users.address ASC").first
p.user_address
#Rails 2
p = Problem.find(:first, :include => :user, :order => "users.address ASC")
p.user_address
It might also be wise to check if a user exist for a problem when
def user_address
self.user.try(:address) #just in case a problem doesn't have an associated user
end