I am creating an application in Ruby and I have two model classes
class Restaurant < ActiveRecord::Base
attr_accessible :title, :cuisine, :price_range, :environment
self.primary_key = 'title'
has_many :environments
end
end
class Environment < ActiveRecord::Base
attr_accessible :title, :type
belongs_to :restaurants,foreign_key: 'title'
end
I am trying to query two tables in the controller- to return restaurants with specific environments.
#restaurants = Restaurant.joins('INNER JOIN environments ON restaurants.title=environments.title').where('environments.env_type'=> #selected_environment)
NOTE: #selected_environment is a hash contained list of environments. Environments table has a list of environment and restaurant pairs(there can be more than one restaurant).
I know the query above is not correct to achieve my goal. Is there a way i can get this done?
controller method:
def index
# can later be moved to the Restaurant model
ordering = {:title => :asc}
#all_environments = Restaurant.all_environments
#selected_environments = params[:environments] || session[:environments] || {}
if #selected_environments == {}
#selected_environments = Hash[#all_environments.map {|environment| [environment, environment]}]
end
if params[:environments] != session[:environments]
session[:environments] = #selected_environments
redirect_to :environments => #selected_environments and return
end
#restaurants = Restaurant.joins(:environments).where(environments:{env_type: #selected_environments.keys })
end
For your models you want to do this:
class Restaurant < ActiveRecord::Base
attr_accessible :title, :cuisine, :price_range, :environment
self.primary_key = 'title'
has_many :environments, :foreign_key => 'title', :primary_key => 'title'
end
class Environment < ActiveRecord::Base
attr_accessible :title, :type
belongs_to :restaurants, foreign_key: 'title'
end
Try structuring your query like this:
Restaurant.joins(:environments).where(environments: { env_type: #selected_environments.values })
Related
I develop engine for Ruby on Rails project. Here you can see how I solve the problem of eager loading associated objects with dynamic condition:
My models:
#version.rb
class Version < ActiveRecord::Base
class <<self
attr_accessor :type
attr_accessor :id
end
validates :name, :description, presence: true
belongs_to :package
has_many :clustervers,:dependent => :destroy
has_many :accesses,:dependent => :destroy
has_many :user_accesses,-> { where( "who_type= ? AND who_id=?",Version.type,Version.id) }, class_name: "Access"
end
#Access.rb
class Access < ActiveRecord::Base
enum status: [:request,:allowed,:denied]
validates :version_id, :user_id,presence: true
validates_uniqueness_of :user_id, :scope => [:version_id]
belongs_to :version
belongs_to :user
belongs_to :who, :polymorphic => true
end
Controller:
def show
Version.type="User"
Version.id=2
#package = Package.find(params[:id])
#versions = Version.page(params[:page]).per(2).includes({clustervers: :core_cluster},:user_accesses)
.where(package_id:params[:id])
end
View:
for version in #versions
tr
td #{version.name}
td #{version.description}
td #{version.r_up}
td #{version.r_down}
-if version.user_accesses.take
p show attributes
-else
p no access
td
Are there more convenient methods to do this? Is it possible to do this using raw SQL statments?
I need something to generate SQL condition like this:
LEFT OUTER JOIN ... ON a.id=b.id and CONDITION
In Rails project, I am trying to generate array of Category's name if it belongs to Single User.
For example, User A have two categories like LandLord and PropertyBroker.
User Model
class User < ActiveRecord::Base
has_many :users_categories
has_many :categories, through: :users_categories
scope :users_categories_active, -> { joins(:users_categories)
.where('users_categories.status = ?', true) }
def self.with_selected_active_user
select("users.id, users.first_name, users.city, categories.name,
users_categories.status, users_categories.id")
end
end
Category Model
class Category < ActiveRecord::Base
has_many :users_categories
has_many :users, through: :users_categories
end
UserCategory Model
class UserCategory < ActiveRecord::Base
belongs_to :user
belongs_to :category
end
Query
user_list = User.joins(:categories, :users_categories)
.with_selected_active_user.users_categories_active.uniq
render :json => user_list
Output
[{"id":2,"first_name":"Adam","city":"Mumbai","status":true,"name":"LandLord"},
{"id":3,"first_name":"Charles","city":"Delhi","status":true,"name":"Tenant"},
{"id":1,"first_name":"Adam","city":"Mumbai","status":true,"name":"ProperyBroker"}
]
I am trying to generate this output in different patter
[{"first_name":"Adam","city":"Mumbai","status":true,"name":"['LandLord','ProperyBroker'"},
{"first_name":"Charles","city":"Delhi","status":true,"name":"Tenant"}
]
To summarise, I'd like to put category name in one array.
Use group in your query, like below -
user_list = User.joins(:categories, :users_categories)
.with_selected_active_user.users_categories_active.group(" first_name, city, status, name ")
render :json => user_list
[{"id" => 2,"first_name" => "Adam","city" =>"Mumbai","status" => true,"name" => "LandLord"},
{"id" => 3,"first_name" => "Charles","city" => "Delhi","status" => true,"name" => "Tenant"},
{"id" => 1,"first_name" => "Adam","city" => "Mumbai","status" => true,"name" => "ProperyBroker"}
].reduce({}) do |m, e|
found = (m[e["city"]] ||= {status: e['status'], names: [], city: e['city']})
found[:names] << e['name']
m
end.values
=> [{:status=>true, :names=>["LandLord", "ProperyBroker"], :city=>"Mumbai"},
{:status=>true, :names=>["Tenant"], :city=>"Delhi"}]
I have post model
class Post < ActiveRecord::Base
acts_as_voteable
end
and Vote model
class Vote < ActiveRecord::Base
scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.name]) }
scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.name]) }
scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) }
scope :descending, order("created_at DESC")
belongs_to :voteable, :counter_cache=>true,:polymorphic => true,:touch=>true
belongs_to :voter, :polymorphic => true
attr_accessible :vote, :voter, :voteable
# Comment out the line below to allow multiple votes per user.
validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]
end
when I get the post voters with these method
<% #post.voters_who_voted.each do |voter|%>
<%= voter.name %>
<% end %>
I load my database
how can I select only the user name and user id from these array?
update I changed my code I am using thumbs_up gem I pasted less code first to simplify the question
What do you mean by "load database"? If you want to select only id and name columns, then use #post.users.select([:id, :name]).each ...
Or is it about this problem (according to code that you provided)?
UPD.
voters_who_voted loads all voters and returns array https://github.com/bouchard/thumbs_up/blob/master/lib/acts_as_voteable.rb#L113. You have to add own association to Post model:
has_many :voters, :through => :votes, :source => :voter, :source_type => 'User'
It's just example, perhaps voters will clash with already existing method, if any.
Then use it here instead of voters_who_voted
did you try collect method ??
names = #post.users.collect(&:name)
ids = #post.user.collect(&:id)
If you want it to be related you can make a HASH with it. Id's mapped to the names.
This is my code for moving data from my old database:
class Old < ActiveRecord::Base
establish_connection :old_version
self.abstract_class = true
class Recipe < self
set_table_name :recipes
has_many :uploaded_files, :as => :storage
end
class UploadedFile < self
set_table_name :uploaded_files
belongs_to :storage, :polymorphic => true
end
end
When I run the following code
Old::Recipe.all.each do |recipe|
puts recipe.uploaded_files.to_sql
end
It performs this SQL
SELECT `uploaded_files`.* FROM `uploaded_files` WHERE `uploaded_files`.`storage_id` = 38 AND `uploaded_files`.`storage_type` = 'Old::Recipe'
The problem is that I get:
`storage_type` = 'Old::Recipe'
But I need:
`storage_type` = 'Recipe'
How can I change the class for a polymorphic relationship?
The doc for has_many doesn't give me an answer.
Recently I had similar problem, this is a solution that worked for me in rails 4.2:
class Recipe < self
set_table_name :recipes
has_many :old_files, -> (object) { unscope(where: :storage_type).where(storage_type: 'Recipe') }, class_name: 'UploadedFile'
end
You have to add unscope(:where) to remove condition uploaded_files.storage_type = 'Old::Recipe' from query.
The answer by santuxus above is working properly for rails 4.2+
However, for lower versions, you could try overwriting the association like so:
class Recipe
has_many :uploaded_files, conditions: { storage_type: 'Recipe' }, foreign_key: :storage_id
end
Unfortunately, for now I found only one way to do that:
class Old < ActiveRecord::Base
establish_connection :old_version
self.abstract_class = true
class Recipe < self
set_table_name :recipes
has_many :old_files,
:class_name => 'UploadedFile',
:finder_sql => Proc.new {
%Q{
SELECT uploaded_files.*
FROM uploaded_files
WHERE uploaded_files.storage_id = #{id} AND
uploaded_files.storage_type = 'Recipe'
}
}
end
class UploadedFile < self
set_table_name :uploaded_files
belongs_to :storage, :polymorphic => true
end
end
namespace :old do
task :menu => :environment do
Old::Recipe.all.each do |recipe|
puts '~' * 50
puts recipe.id
recipe.old_files.to_a.each do |file|
puts file.storage_id, file.storage_type
end
end
end
end
very very sad
I have a Card model that has many CardSets and a CardSet model that has many Cards through a Membership model:
class Card < ActiveRecord::Base
has_many :memberships
has_many :card_sets, :through => :memberships
end
class Membership < ActiveRecord::Base
belongs_to :card
belongs_to :card_set
validates_uniqueness_of :card_id, :scope => :card_set_id
end
class CardSet < ActiveRecord::Base
has_many :memberships
has_many :cards, :through => :memberships
validates_presence_of :cards
end
I also have some sub-classes of the above using Single Table Inheritance:
class FooCard < Card
end
class BarCard < Card
end
and
class Expansion < CardSet
end
class GameSet < CardSet
validates_size_of :cards, :is => 10
end
All of the above is working as I intend. What I'm trying to figure out is how to validate that a Card can only belong to a single Expansion. I want the following to be invalid:
some_cards = FooCard.all( :limit => 25 )
first_expansion = Expansion.new
second_expansion = Expansion.new
first_expansion.cards = some_cards
second_expansion.cards = some_cards
first_expansion.save # Valid
second_expansion.save # **Should be invalid**
However, GameSets should allow this behavior:
other_cards = FooCard.all( :limit => 10 )
first_set = GameSet.new
second_set = GameSet.new
first_set.cards = other_cards # Valid
second_set.cards = other_cards # Also valid
I'm guessing that a validates_uniqueness_of call is needed somewhere, but I'm not sure where to put it. Any suggestions?
UPDATE 1
I modified the Expansion class as sugested:
class Expansion < CardSet
validate :validates_uniqueness_of_cards
def validates_uniqueness_of_cards
membership = Membership.find(
:first,
:include => :card_set,
:conditions => [
"card_id IN (?) AND card_sets.type = ?",
self.cards.map(&:id), "Expansion"
]
)
errors.add_to_base("a Card can only belong to a single Expansion") unless membership.nil?
end
end
This works! Thanks J.!
Update 2
I spoke a little too soon. The above solution was working great until I went to update an Expansion with a new card. It was incorrectly identifying subsequent #valid? checks as false because it was finding itself in the database. I fixed this by adding a check for #new_record? in the validation method:
class Expansion < CardSet
validate :validates_uniqueness_of_cards
def validates_uniqueness_of_cards
sql_string = "card_id IN (?) AND card_sets.type = ?"
sql_params = [self.cards.map(&:id), "Expansion"]
unless new_record?
sql_string << " AND card_set_id <> ?"
sql_params << self.id
end
membership = Membership.find(
:first,
:include => :card_set,
:conditions => [sql_string, *sql_params]
)
errors.add_to_base("a Card can only belong to a single Expansion") unless membership.nil?
end
I'm really not sure about that, I'm just trying because I'm not able to test it here... but maybe something like the following works for you. Let me know if it does :]
class Expansion < Set
validate :validates_uniqueness_of_cards
def validates_uniqueness_of_cards
membership = Membership.find(:first, :include => :set,
:conditions => ["card_id IN (?) AND set.type = ?",
self.cards.map(&:id), "Expansion"])
errors.add_to_base("Error message") unless membership.nil?
end
end
Very late to the party here, but assuming you've set up STI, then you can now validate uniqueness of an attribute scoped to the sti type,
e.g
validates_uniqueness_of :your_attribute_id, scope: :type