ActiveRecord query on model methods - ruby-on-rails

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

Related

Get controller#index with joins to pass with rspec

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.

Rails has_many through cannot save in nested form due to id = null

Using Rails 4.1.13 and Ruby 2.0.0 (although I had the same problem with Ralis 4.0 and Ruby 1.9.3. I have read numerous articles about this particular issue and cannot understand why my solution (which seems exactly like this) does not work, so please help me out.
I have two models BlogPost and Tag. A BlogPost can have many Tags and one Tag can have many BlogPosts. I connect them through a third model BlogPostRelation. Thus, this is my basic setup:
# blog_post.rb
has_many :blog_post_tag_relations, dependent: :destroy
has_many :tags, :through => :blog_post_tag_relations
accepts_nested_attributes_for :blog_post_tag_relations, :tags
# tag.rb
has_many :blog_post_tag_relations, dependent: :destroy
has_many :blog_posts, :through => :blog_post_tag_relations
# blog_post_tag_relation.rb
belongs_to :tag
belongs_to :blog_post
validates_uniqueness_of :tag_id, :scope => [:blog_post_id]
validates :blog_post_id, :presence => true
validates :tag_id, :presence => true
accepts_nested_attributes_for :tag, :blog_post
I have a form for BlogPost, using Formtastic, where I create checkboxes for the BlogPost using:
<%= f.input :blog_title %>
<%= f.input :tags, as: :check_boxes, :collection => tags.order(:name) %>
The problem I have is that BlogPost is not saved before the Tags are added which causes an validation failure of blog_post_id not being present (which it isn't):
Tag Load (1.6ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" IN (678, 56)
(0.9ms) BEGIN
BlogPost Exists (1.6ms) SELECT 1 AS one FROM "blog_posts" WHERE ("blog_posts"."id" IS NOT NULL) AND "blog_posts"."slug" = 'de22323' LIMIT 1
BlogPostTagRelation Exists (1.2ms) SELECT 1 AS one FROM "blog_post_tag_relations" WHERE ("blog_post_tag_relations"."tag_id" = 678 AND "blog_post_tag_relations"."blog_post_id" IS NULL) LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "blog_posts" WHERE ("blog_posts"."id" IS NOT NULL) AND "blog_posts"."slug" = 'de22323' LIMIT 1
BlogPostTagRelation Exists (1.1ms) SELECT 1 AS one FROM "blog_post_tag_relations" WHERE ("blog_post_tag_relations"."tag_id" = 56 AND "blog_post_tag_relations"."blog_post_id" IS NULL) LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "blog_posts" WHERE ("blog_posts"."id" IS NOT NULL) AND "blog_posts"."slug" = 'de22323' LIMIT 1
(0.8ms) ROLLBACK
It seems like the solution should be to use inverse_of, which I frankly don't understand to 100%. It should also be mentioned that I am not 100% sure on how to use accepts_nested_attributes_for either for this type of issue. I have tried all different setups but as far as I understand the only place they should be is in the join model, BlogPostRelation, like this:
# blog_post_tag_relation.rb
belongs_to :tag, :inverse_of => :blog_post_tag_relations
belongs_to :blog_post, :inverse_of => :blog_post_tag_relations
validates_uniqueness_of :tag_id, :scope => [:blog_post_id]
validates :blog_post_id, :presence => true
validates :tag_id, :presence => true
accepts_nested_attributes_for :tag, :blog_post
This doesn't work either and I am completely lost now in what to do.
Most important: What should I do?
Is inverse_of the solution to this problem? If so, how should I use it?
Am I using accepts_nested_attributes_for correctly?
Does it have to do with the naming of BlogPostTagRelation (should it have been called BlogPostTag instead?
Part of the problem here is that you are validating on ids. Rails cannot validate that blog_post_id is present if the id is not known, but it can validate that blog_post is present.
So part of the answer at least is to validate the presence of the associated instance, not the id.
Change the validations to:
validates :blog_post, :presence => true
validates :tag , :presence => true
I would always specify inverse_of as well, but I'm not sure it is part of this problem.
Your model structure is okay.
There's one nifty way you can add tags to your posts after the post is created. You just need to use a model method for doing that.You do not need inverse_of . Here is how:
In your view, add a custom attribute (all_tags).
<%= f.text_field :all_tags, placeholder: "Tags separated with comma" %>
You need to permit the parameter in the controller.
In your Post model add these three methods:
def all_tags=(names)
self.tags = names.split(",").map do |name|
Tag.where(name: name.strip).first_or_create!
end
end
#used when the post is being created. Splits the tags and creates entries of them if they do not exist. `self.tags` ensures that they tags will be related to the post.
def all_tags
self.tags.map(&:name).join(", ")
end
#Returns all the post tags separated by comma
def self.tagged_with(name)
Tag.find_by_name!(name).posts
end
#Returns all the posts who also contain the tag from the current post.
Here's a full implementation
You are using nested_attributes_for correctly, but in this case, you are having models who just have a name and a belongs_to column, so using this is an overkill.
You can call it a tagging, although there's no convention for naming. If you and other can understand it, it is fine.
What you really want to use is a has_and_belongs_to_many (HABTM) association: http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
This however assumes you do not want to do anything with the Relationship Model (blog_post_tag_relations in your case)
You would need only the following models and associations:
class BlogPost < ActiveRecord::Base
  has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
  has_and_belongs_to_many :blog_posts
end
You would then have to rename your join table blog_post_tag_relations to blog_posts_tags, the alphabetical plural combination of the two models. Rails automagically looks up/uses that table seamlessly in the background. It will only have the relationship's foreign_keys:
create_table :blog_posts_tags, id: false do |t|
t.belongs_to :blog_post, index: true
t.belongs_to :tag, index: true
end
Then your form just works:
<%= f.input :blog_title %>
<%= f.input :tags, as: :check_boxes, :collection => tags.order(:name) %>
try
validates :blog_post, :presence => true
validates :blog_post_id, :presence => true, allow_nil: true #can ignore this

missing attribute error Rails

I cant seem to get the result of a scope to display in my view as i get this error message.I am trying to get all of the memberships amounts for the day added up and displayed as a total
missing attribute: membership_id
My Models and scope
class Member < ActiveRecord::Base
belongs_to :membership
accepts_nested_attributes_for :membership
attr_accessible :membership_id, :forename, :middlename, :surname, :house_no, :house_name, :street, :town, :postcode, :home_tel, :mobile_tel, :work_tel, :email, :start_date, :expiry_date
scope :new_memberships_cash_today, ->() {
joins(:membership).where(:start_date => Date.today).select('ROUND(SUM(memberships.cost), 2)')
}
end
class Membership < ActiveRecord::Base
has_many :members, :dependent => :destroy
attr_accessible :membership_type, :cost
end
And then my view
columns do
#Total amount in £ for New Memberships today
column do
panel "Cash Today", :class => 'NewAmountMemberships' do
table_for Member.new_memberships_cash_today do
column 'Total cash' do |c|
c.membership.map { |e| [e.cost, e.id] }
end
end
end
end
end
After some reading it would seem that there may be an issue with my select call in the scope, as i need to specify all of the models attributes to make a successful call with Active Record?
As i am performing a sum within the select i am unsure how to add more attributes, if this is even the case
Any help appreciated
i have run the scope in the the console and this is what is returned
Member Load (0.1ms) SELECT ROUND(SUM(memberships.cost), 2) FROM `members` INNER JOIN `memberships` ON `memberships`.`id` = `members`.`membership_id` WHERE `members`.`start_date` = '2013-12-13'
=> #<ActiveRecord::Relation [#<Member id: nil>]>
I wouldn't do this in a scope, but in a helper method. That way you can grab the associated records and just call your method to return the total.
Something along the lines of this:
def membership_sum(memberships = [])
sum = 0
memberships.each { |membership| sum += membership.cost }
sum.round
end
Now, store the associated records in a #memberships variable (from within your controller) and then, in your view, use <%= membership_sum(#memberships) %>

Rails - Query One Model from within a Different Model with a has_many & belongs_to relationship

I'm not entirely clear on how to query a models data from within a different model in Rails. The models have a has_many & belongs_to relationship. The 2 models are "Gear" and "line_item". Gear has_many line_items and LineItem belongs_to Gear.
What I'm trying to do is query all the line_items in the database that belong to a Gear object that have a NULL or Blank cart_id (this is one of the fields) and then calculate it's availability. Since in this case the Gear is rented out at certain dates and times that are stored in a line_item (start_date, end_date, start_hour, end_hour) ..Having not done a lot of advanced quering in Ruby I Googled around and now I'm not sure if how I should use
Enumerable in the Gear Model with something like this:
line_items.inject(0) {|line_item| line_item.where('line_items.cart_id' => NULL }
Or if I could use a scope in the Gear Model like this:
scope :availablegear, lambda { includes(:line_items).where('line_items.cart_id' => nil) }
I know the syntax on both is probably not correct so I could use some direction on what to use and how to use it.
Thanks.
MY Project is using Rails 3.2.0, Ruby 1.9.4 & MySQL
EDIT with Suggested Answer
class Gear < ActiveRecord::Base
attr_accessible :title, :size, :price, :sub_category_id, :user_id, :image, :image_a, :image_b, :image_c, :image_d, :image_e, :image_f, :image_g, :image_h, :image_i, :remote_image_url, :color, :year, :latefee, :cancellation, :minrental, :policy, :about, :address, :city, :state, :zip, :country, :latitude, :longitude, :gmaps
...some code omitted for brevity
has_many :line_items
scope :availablegear, joins(:line_items).where(:line_items => {:cart_id => nil})
...some code omitted for brevity
end
Now when I boot up Rails console and I do a g = Gear.find(4) and then do g.availablegear I get the following error: NoMethodError: undefined method `availablegear' for #
I think the query you're looking for is
Gear.joins(:line_items).where(:line_items => {:cart_id => nil})
You can put it in a scope in the Gear class:
class Gear< ActiveRecord::Base
scope :available, joins(:line_items).where(:line_items => {:cart_id => nil})
end
You can find some more help in the Rails Query Guide (see 11.3 for joining with conditions).
If you're trying to get line items back:
class Gear < ActiveRecord::Base
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :gear
scope :available, where(:cart_id => nil)
end
Then if you have a gear, you can call
gear.line_items.available
which will return line items that belong to gear and have no cart_id.

Rails Format Collection_Select Based on two relationships

I'm trying to figure out how to construct a collection_select to include two relationships. Here are my models:
class Country < ActiveRecord::Base
has_many :companies, :dependent => :destroy
end
class Company < ActiveRecord::Base
belongs_to :country
has_many :departments, :dependent => :destroy
end
class Department < ActiveRecord::Base
belongs_to :company
end
When I create a new company I use the following to show a select box based on the relationship.
<%= collection_select(:company, :country_id, Countries.all, :id, :name, :prompt => 'Please select country') %>
But for the departments I'd like to have a select which let's the user select it's company from a select which also includes the companies country, formatted in the following way:
Company 1 - Country 1
Company 2 - Country 1
If i use the following I will only get a list of all the companies which I'd like to be able to see from the list which country they are from.
<%= collection_select(:device, :cabinet_id, Cabinet.all, :id, :name, :prompt => 'Please select cabinet') %>
Is there a way for rails to pull the information for the country into a select and append the entry with it's parent country?
I hope I've worded this question correctly! Sorry if it isn't clear.
Even if #jvnil solution works, I think you should avoid putting this logic in your view.
Instead, you could create an instance method in your Company model and use it in your select.
In your model :
class Company< ActiveRecord::Base
def name_for_select
name + " - " + country.name
end
end
And in your view :
<%= collection_select(:department, :company_id, Company.all, :id, :name_for_select %>
Use
UPDATE: move logic code to model
# company.rb
def company_with_country
"#{name} - #{country.name}" # this is better than using `string + string` since it creates only 1 string
end
# view
collection_select :department, :company_id, Company.includes(:country).all, :id, :company_with_country
UPDATE: faster version because it only uses needed columns
# controller
#companies = Company.joins(:country)
.select('companies.id, companies.name, countries.name AS country_name')
.map { |c| ["#{c.name} - #{c.country_name}", c.id] }`
# view
select :department, :company_id, #companies

Resources