Default scope for a joined table in Rails 4 - ruby-on-rails

I have to select only program schedule where it belongs to a channel with ctype = TV
but I get this error:
Program (Model)
class Program < ActiveRecord::Base
has_many :program_schedules, dependent: :delete_all
ProgramSchedule (Model)
class ProgramSchedule < ActiveRecord::Base
belongs_to :program
belongs_to :channel
default_scope { includes(:channel).where("program_schedules.channels.ctype NOT IN (?)", ['tv']) }
end
Channel (Model)
class Channel < ActiveRecord::Base
validates :name, :site, :ctype, :country, :presence => true
has_many :programs, :dependent => :delete_all
QUERY
#programs = Program.includes(:program_schedules).where(
"programs.ptype" => ["tv_show","movie"],
"programs.country" => #country).
select("programs.id").
group("programs.id, program_schedules.id, channels.id").
having("program_schedules.stop > ?", Time.current)
Schema.rb
create_table "channels", force: true do |t|
t.string "site"
t.string "name"
t.string "icon"
t.string "ctype"
t.string "country"
t.string "lang"
t.integer "ordertab"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "homepage"
end
create_table "programs", force: true do |t|
t.string "country"
t.string "title"
t.text "subtitle"
t.text "description"
t.float "official_rating", default: 0.0
t.float "rating", default: 0.0
t.string "ptype"
t.string "year"
t.string "imdb"
t.string "tmdb"
t.string "tvdb"
t.string "wiki"
t.string "poster"
t.string "backdrop"
t.string "trailer"
t.float "popularity", default: 0.0
end
create_table "program_schedules", force: true do |t|
t.integer "program_id"
t.datetime "start"
t.datetime "stop"
t.integer "channel_id"
end

Your includes should include the association not the table name in symbolic form. Try changing your default scope to the following:
default_scope { includes(:channel).where("channels.ctype NOT IN (?)", ['tv']) }

I'm posting this as an "answer", but it is intended as a comment. I am doing this so that I can illustrate my explanation better. Let me know if / when you'd like me to remove this non-answer.
I have tried to replicate your problem, and I simply cannot do so using the query and default_scope you've posted so far. Ultimately, the problem is that your LEFT OUTER JOIN condition is referencing a table that has not yet been joined. Even though it is joined later in the query, the interpreter cannot see that far ahead.
This is the part I'm referring to (formatted for readability):
...FROM "programs"
LEFT OUTER JOIN
"program_schedules"
ON "program_schedules"."program_id" = "programs"."id"
AND (channels.ctype NOT IN ...)
^^^
Problem is here. The channels table has not yet been joined.
Additionally, I have never seen a .where appending to the join condition. If you're using that method, ActiveRecord should be placing whatever you passed to the .where method in the WHERE clause, as you'd expect. For some reason, it is obviously appending your condition to the LEFT OUTER JOIN as an AND condition.
The only way I know to append conditions to the join, is by using the .joins method, something like this:
...joins(" AND program_schedules.channels.ctype NOT IN (?)", ['tv'])
That will indeed result in a query like this (truncated for brevity):
LEFT OUTER JOIN "program_schedules" ON ... AND program_schedules.channels......
In addition, the generated query in the error message states
(channels.ctype NOT IN ...)
as the failing join condition, whereas your .where condition states
program_schedules.channels.ctype
I don't see anywhere else in your question where you've written anything that looks like that part of the query in the error message. So again, it seems like we're not quite seeing everything.
It seems like we are still missing some pieces of information. Is it possible you do indeed have something like the .join condition I wrote above? Are there other default_scopes in other models that could be doing this?

I stumbled upon this StackOverflow question when I was researching a similar issue that I'm seeing in my application. I think that using default_scope with joins is just broken.
I boiled your code down to something simple that would fail, ran Program.joins(:program_schedules).to_sql and got the following sql:
SELECT "programs".*
FROM "programs"
INNER JOIN "program_schedules" ON
"program_schedules"."program_id" = "programs"."id" AND
("channels"."ctype" NOT IN ('tv'))
The problem isn't that it hasn't joined to channels yet, it is that it will never join to channels. The where clause from the default_scope got tacked into the INNER JOIN, but the join was discarded.
class Channel < ActiveRecord::Base
has_many :programs
end
class Program < ActiveRecord::Base
has_many :program_schedules
end
class ProgramSchedule < ActiveRecord::Base
belongs_to :program
belongs_to :channel
default_scope { joins(:channel).where.not(channels: { ctype: ['tv'] }) }
end
ActiveRecord::Schema.define(version: 20140331124327) do
create_table "channels", force: true do |t|
t.string "site"
t.string "name"
t.string "icon"
t.string "ctype"
t.string "country"
t.string "lang"
t.integer "ordertab"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "homepage"
end
create_table "programs", force: true do |t|
t.string "country"
t.string "title"
t.text "subtitle"
t.text "description"
t.float "official_rating", default: 0.0
t.float "rating", default: 0.0
t.string "ptype"
t.string "year"
t.string "imdb"
t.string "tmdb"
t.string "tvdb"
t.string "wiki"
t.string "poster"
t.string "backdrop"
t.string "trailer"
t.float "popularity", default: 0.0
end
create_table "program_schedules", force: true do |t|
t.integer "program_id"
t.datetime "start"
t.datetime "stop"
t.integer "channel_id"
end
end

Please take a look at: https://github.com/rails/rails/issues/12896
In activerecord-4.1.7/lib/active_record/scoping/default.rb there's a comment with the next:
# If you need to do more complex things with a default scope, you can
# alternatively define it as a class method:
#
class Article < ActiveRecord::Base
def self.default_scope
# Should return a scope, you can call 'super' here etc.
end
end
Which worked for me.

Related

RoR top100 photos

I must create page with top100 photos by visits, by comments... but i dont know how to make it. Anybody help me please :)
Error:
undefined method `Visit' for <Photo::ActiveRecord
photos_controller
....
def top100
#photos = Photo.all
#photos.visit.count(limit: 10)
end
routes
get 'photos/top100'
schema
create_table "visits", force: true do |t|
t.integer "photo_id"
t.integer "user_id"
t.integer "count", default: 0
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "photos", force: true do |t|
t.string "name"
t.text "description"
t.integer "user_id"
t.string "image"
t.string "tags"
t.string "camera"
t.string "lens"
t.hstore "settings"
t.integer "im_rating", default: 0
t.integer "im_visits", default: 0
t.integer "im_votes", default: 0
t.boolean "is_ban_comments", default: false
t.datetime "created_at"
t.datetime "updated_at"
end
photo.rb
has_many :visits, dependent: :destroy
visit.rb
belongs_to :photo
belongs_to :user
Use SQL grouping and ordering:
# Top photos by visits
#top_photos = Visit.select("photo_id, COUNT(*) AS visit_count").includes(:photo).group(:photo_id).order("visit_count DESC").map(&:photo)
More on ordering by count in SQL: SQL Order By Count
Notice #includes(:photo) – that way the following #map(&:photo) call won't produce N queries (just a single one to fetch them all).
Additionally, it seems you didn't specify relationship in your models, so add this line to visit.rb:
belongs_to :photo
and this line to photo.rb
has_many :visits
unles you have them there already.

How do I select the name field in both this and an associated record in rails?

I have two models, "Thing" and "Category", defined like this:
class Thing < ActiveRecord::Base
belongs_to :category
scope :category_name, lambda { |name| joins(:category).where("name in (?)", name) }
# need to order by category and thing name eventually
scope :order_alphabetically, ->{ order("name")}
end
class Category < ActiveRecord::Base
has_many :thing
end
and the schema for these look like:
ActiveRecord::Schema.define(version: 20150120222942) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "things", force: :cascade do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.text "name"
t.text "state", default: "active"
t.boolean "enabled", default: true
t.integer "category_id"
end
create_table "categories", force: :cascade do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.text "name"
t.text "description"
end
I want to be able to do two things:
a) Sort things by thing name then category name.
b) Select just the pair (thing.name, category.name) (and maybe with the same sort order).
Oh, and I'm using postgresql as the datastore, if that makes a difference.
You could join and sort:
Thing.joins(:comment).order('things.name, categories.name')
And then pluck these names:
Thing.joins(:comment).order('things.name, categories.name').pluck('things.name', 'categories.name')

Rails one to many through a relationship table

I have a Company that has many Users through a join table company_user. Each user should work for only one Company. This is a 1 to many relationship.
I have looked around for this and found the solution in https://stackoverflow.com/a/7080017/883102
But I get the error
PG::UndefinedTable: ERROR: relation "companies" does not exist
LINE 5: WHERE a.attrelid = '"companies"'::regclass
When I try to create a Company. How can I solve this?
My models are
Company
class Company < ActiveRecord::Base
has_many :employments
has_many :users, :through => :employments
end
Users
class User < ActiveRecord::Base
...
end
Employment
class Employment < ActiveRecord::Base
belongs_to :company
belongs_to :user
end
The migration for my join table is
create_table :employment do |t|
t.belongs_to :company
t.belongs_to :user
t.timestamps
end
My schema.rb
create_table "company", force: true do |t|
t.integer "rating"
t.integer "phone"
t.string "name"
t.string "address"
t.string "email"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "employment", id: false, force: true do |t|
t.integer "company_id"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.string "name"
t.string "email"
t.datetime "created_at"
t.datetime "updated_at"
t.string "password_digest"
t.string "remember_token"
t.string "role"
end
Hi I found the answer here
https://stackoverflow.com/a/24318236/883102
The problem was that my table names were in the singular form, I changed these in the migration and then re-created the database. It all seems to be working fine now.
My User class ended up as
class User < ActiveRecord::Base
has_one :employment
has_one :company, :through => :employment
end
This was to allow bi-directional associations

Which relationship is good to use STI or Polymorphism?

I have read about them but still not clear to me which one I suppose to use and how.
I have User model, Message model and Place model
Message model:
class Message < ActiveRecord::Base
belongs_to :user
end
Messages Table:
create_table "messages", force: true do |t|
t.string "title"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
User model:
class User < ActiveRecord::Base
has_many :messages
end
Users Table:
create_table "users", force: true do |t|
t.string "email"
t.string "password_digest"
t.datetime "created_at"
t.datetime "updated_at"
t.string "username"
end
Now, what I want to do is:
"USER" says "MESSAGES" from "PLACES"
eg. "AHMED" says "HELLO" from "EARTH"
For me both Models (Message and Place) have same data (data type) and same behaviours. So places table should be:
create_table "places", force: true do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
Now may be I'm confused or making big deal than it should be.
What kind of relation should Message and Place have? should it be STI or Polymorphism?
How should I decide?
I'd appreciate the thinking process of how and why I decide specific association.
This example, despite Messages and Places having the same data, doesn't seems a STI/Polymorphism scenario and they should have two different tables.
This could work as a solution:
create_table "users" do |t|
t.string "username"
end
create_table "messages" do |t|
t.string "text"
t.integer "user_id"
t.integer "place_id"
end
create_table "places" do |t|
t.string "name"
end
class User < ActiveRecord::Base
has_many :messages
has_many :places, through: :messages
end
class Place < ActiveRecord::Base
end
class Message < ActiveRecord::Base
belongs_to :user
belongs_to :place
def to_s
"#{user.username} says #{title} from #{place.name}"
end
end
ahmed = User.new(username: "AHMED")
earth = Place.new(name: "EARTH")
message = Message.new(text: "HELLO", user: ahmed, place: earth)
puts message
# => "AHMED says HELLO from EARTH"

TypeError Conversion on has_many relationship

I have a couple of objects in a Rails app ("Ticket", and "Comment")
class Ticket < ActiveRecord::Base
has_many :attributes
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :ticket
belongs_to :user
end
with the following schema:
create_table "comments", :force => true do |t|
t.integer "ticket_id"
t.integer "user_id"
t.text "content"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "tickets", :force => true do |t|
t.integer "site_id"
t.integer "status"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
However, for some reason - whenever I do a #lead.comments I get a crash:
can't convert String into Integer
Any ideas? This is driving me nuts!
I think the line that's causing you pronlems is:
has_many :attributes
"attributes" is a special word in an Active Record. It refers to the values of the columns in the db.
If you try and override this with an association, then you will have problems.
My suggestion is that you should not have a model called an "attribute" - call it something else, eg "properties", and the problems will go away.

Resources