The code is simply inherited model.
But unfortunately validation does not work. The idea is to select fathers from parrots. When I loop #fathers - it shows all parrots but should select only those who have age more than 12 and so on. Or maybe I do something wrong?
Model
class Father < Parrot
has_many :parrots
validates :age, :numericality => { :greater_than => 12}
validates :tribal, :acceptance => true
validates_inclusion_of :sex, :in => %w( Male )
end
view
<% #fathers.each do || %>
<%= f.name %>
<% end %>
controller
def index
#parrots = Parrot.all
#fathers = Father.all
end
The validation criteria has nothing to do with how data is queried, just that it passes the defined criteria before object is written to the database. Are you saying that Father object doesn't perform the validation and persists invalid data?
Are you sure that all of the father parrots are being saved through the Father object? You should also have a type column in your parrots column that has either 'Parrot' or 'Father' value. When you execute Father.all it should be running a query that looks like this:
SELECT * FROM parrots WHERE type='Father';
filtering out parrots that were not saved through the Father object.
If you just need to pull Parrots that match Father criteria from the DB you can use scopes:
class Father < ActiveRecord::Base
self.table_name = 'parrots'
default_scope { where("age > 12 and tribal = 'true' and sex='Male'")}
#whatever else
end
Here's additional information on Single Table Inheritance and scopes
Validations are used when the Father is saved. The "type" column in the db will determine the class. Be sure that is set correctly.
But, is Father really a separate class? Or just a Parrot with certain attributes. It seems that Parrot.all would include all Fathers.
It may be a scope.
class Parrot < ActiveRecord::Base
scope :fathers, -> { where(:sex => 'Male').where('age >= 12').where(:tribal => true) }
end
Then you can get fathers by
#fathers = Parrot.fathers
Related
Looks like my form does what is expected to do - sends the right value, but its not being saved in db. Check_box_tag takes data from enum (I use enum because I use same data for select field):
class UserProfile < ApplicationRecord
enum locations: { Kursenai: 0, Papiskes: 1, Versiai: 2 }
And in form_for:
<% UserProfile.locations.each do |key, val| %>
<%= f.label key %>
<%= check_box_tag('user_profile[locations][]', val, false, id: val) %>
<% end %>
But it fails to update:
'["0", "1"]' is not a valid locations
Postgres:
t.integer "locations", array: true
So I thought it fails because row type is integer, but this:
<%= check_box_tag('user_profile[locations][].to_i', val.to_i, false, id: val) %>
removed error but user field :locations is still nil. What do I miss?
Strong params:
..permit(locations: [])
p.s. if you think this could be done in a better way - please feel free to show.
Why?
Because '["0", "1"]' is considered as string and it is not among values you mentioned in enum i.e 0,1,2.
You can't achieve it directly as enum requires field type to hold single value.But in your case it's an array.
How to achieve?
class UserProfile < ApplicationRecord
# value should store as array not as string.
serialize :locations, Array
# define your own enum by creating static var.U can use Array or Hash structure.
# Here I am using Hash.
# ENUM_LOCATIONS = ["Kursenai", "Papiskes", "Versiai"]
ENUM_LOCATIONS = {"Kursenai": 0, "Papiskes": 1, "Versiai": 2}
# Now modify you getter little bit to return enumed values
def locations
res = []
self[:locations].each{|v| ENUM_LOCATIONS.is_a?(Array) ? res << ENUM_LOCATIONS[v.to_i] : res << ENUM_LOCATIONS.key(v.to_i).to_s}
res
end
end
That's it.
Why are you using enum? I think it's better to create a new model Location and connect with UserProfile via HABTM relation. It would fullfill the Database normalization and easier to work with.
Edit:
class UserProfile < ApplicationRecord
has_and_belongs_to_many :locations
end
class Location < ApplicationRecord
has_and_belongs_to_many :user_profiles
end
and you need to create 3 location records
Location.create(name: 'Kursenai')
Location.create(name: 'Papiskes')
Location.create(name: 'Versiai')
Use any standart queries, joins. You can built a form like here:
Rails 4 - checkboxes for has_and_belongs_to_many association
or Multiple select issue with a HABTM relationship using Rails 4
Rails 4.2.5, Mongoid 5.1.0
I have three models - Mailbox, Communication, and Message.
mailbox.rb
class Mailbox
include Mongoid::Document
belongs_to :user
has_many :communications
end
communication.rb
class Communication
include Mongoid::Document
include Mongoid::Timestamps
include AASM
belongs_to :mailbox
has_and_belongs_to_many :messages, autosave: true
field :read_at, type: DateTime
field :box, type: String
field :touched_at, type: DateTime
field :import_thread_id, type: Integer
scope :inbox, -> { where(:box => 'inbox') }
end
message.rb
class Message
include Mongoid::Document
include Mongoid::Timestamps
attr_accessor :communication_id
has_and_belongs_to_many :communications, autosave: true
belongs_to :from_user, class_name: 'User'
belongs_to :to_user, class_name: 'User'
field :subject, type: String
field :body, type: String
field :sent_at, type: DateTime
end
I'm using the authentication gem devise, which gives access to the current_user helper, which points at the current user logged in.
I have built a query for a controller that satisfied the following conditions:
Get the current_user's mailbox, whose communication's are filtered by the box field, where box == 'inbox'.
It was constructed like this (and is working):
current_user.mailbox.communications.where(:box => 'inbox')
My issue arrises when I try to build upon this query. I wish to chain queries so that I only obtain messages whose last message is not from the current_user. I am aware of the .last method, which returns the most recent record. I have come up with the following query but cannot understand what would need to be adjusted in order to make it work:
current_user.mailbox.communications.where(:box => 'inbox').where(:messages.last.from_user => {'$ne' => current_user})
This query produces the following result:
undefined method 'from_user' for #<Origin::Key:0x007fd2295ff6d8>
I am currently able to accomplish this by doing the following, which I know is very inefficient and want to change immediately:
mb = current_user.mailbox.communications.inbox
comms = mb.reject {|c| c.messages.last.from_user == current_user}
I wish to move this logic from ruby to the actual database query. Thank you in advance to anyone who assists me with this, and please let me know if anymore information is helpful here.
Ok, so what's happening here is kind of messy, and has to do with how smart Mongoid is actually able to be when doing associations.
Specifically how queries are constructed when 'crossing' between two associations.
In the case of your first query:
current_user.mailbox.communications.where(:box => 'inbox')
That's cool with mongoid, because that actually just desugars into really 2 db calls:
Get the current mailbox for the user
Mongoid builds a criteria directly against the communication collection, with a where statement saying: use the mailbox id from item 1, and filter to box = inbox.
Now when we get to your next query,
current_user.mailbox.communications.where(:box => 'inbox').where(:messages.last.from_user => {'$ne' => current_user})
Is when Mongoid starts to be confused.
Here's the main issue: When you use 'where' you are querying the collection you are on. You won't cross associations.
What the where(:messages.last.from_user => {'$ne' => current_user}) is actually doing is not checking the messages association. What Mongoid is actually doing is searching the communication document for a property that would have a JSON path similar to: communication['messages']['last']['from_user'].
Now that you know why, you can get at what you want, but it's going to require a little more sweat than the equivalent ActiveRecord work.
Here's more of the way you can get at what you want:
user_id = current_user.id
communication_ids = current_user.mailbox.communications.where(:box => 'inbox').pluck(:_id)
# We're going to need to work around the fact there is no 'group by' in
# Mongoid, so there's really no way to get the 'last' entry in a set
messages_for_communications = Messages.where(:communications_ids => {"$in" => communications_ids}).pluck(
[:_id, :communications_ids, :from_user_id, :sent_at]
)
# Now that we've got a hash, we need to expand it per-communication,
# And we will throw out communications that don't involve the user
messages_with_communication_ids = messages_for_communications.flat_map do |mesg|
message_set = []
mesg["communications_ids"].each do |c_id|
if communication_ids.include?(c_id)
message_set << ({:id => mesg["_id"],
:communication_id => c_id,
:from_user => mesg["from_user_id"],
:sent_at => mesg["sent_at"]})
end
message_set
end
# Group by communication_id
grouped_messages = messages_with_communication_ids.group_by { |msg| mesg[:communication_id] }
communications_and_message_ids = {}
grouped_messages.each_pair do |k,v|
sorted_messages = v.sort_by { |msg| msg[:sent_at] }
if sorted_messages.last[:from_user] != user_id
communications_and_message_ids[k] = sorted_messages.last[:id]
end
end
# This is now a hash of {:communication_id => :last_message_id}
communications_and_message_ids
I'm not sure my code is 100% (you probably need to check the field names in the documents to make sure I'm searching through the right ones), but I think you get the general pattern.
Given I have two models:
class Post < ActiveRecord::Base
belongs_to :district
end
class District < ActiveRecord::Base
has_many :posts
end
I need to make a check_boxes filter in ActiveAdmin on Posts page for with ability for user to get posts that belong to some exact districts or does not belong to any districts at all.
Before ActiveAdmin changed MetaSearch to Ransack, that could be done with custom scope. And now I don't have any idea. When I do the following:
filter :district_id_null, as: :boolean
filter :district, as: :check_boxes
It makes condition WHERE district_id IN (1,2,3) AND district_id IS NULL (I need OR instead of AND). And when I do
filter :district, as: :check_boxes, collection: proc { District.unscoped.map { |x| [x.title, x.id] }.unshift ['Empty', 'null'] }
It makes condition WHERE district_id IN (0,1,2,3) (but in most SQL databases NULL is not 0).
I think something like this might work
class Post
def self.ransack_with_or(search_params)
params = search_params.deep_clone
#check if we need OR
if search_params.has_key?('district_id_in') && search_params.has_key?('district_id_null')
params.delete('district_id_null')
district_id_in = params.delete('district_id_in')
#old behaviour without district_id_null and district_id_null attributes
q = ransack_without_or(params)
#here we're adding OR group
q.build_grouping({m: 'or', district_id_null: true, district_id_in: district_id_in})
else
#old behaviour we don't need OR
q = ransack_without_or(params)
end
q
end
#wrapping ransack method
class << self
alias_method_chain :ransack, :or
end
end
I have two models:
class Wine
belongs_to :region
end
class Region
has_many :wines
end
I am attempting to use the #where method with a hash built from transforming certain elements from the params hash into a query hash, for example { :region => '2452' }
def index
...
#wines = Wine.where(hash)
...
end
But all I get is a column doesn't exist error when the query is executed:
ActiveRecord::StatementInvalid: PGError: ERROR: column wines.region does not exist
LINE 1: SELECT "wines".* FROM "wines" WHERE "wines"."region" =...
Of course, the table wines has region_id so if I queried for region_id instead I would not get an error.
The question is the following:
Is there a rails-y way to query the Wine object for specific regions using the id in the #where method? I've listed some options below based on what I know I can do.
Option 1:
I could change the way that I build the query hash so that each field has _id (like { :region_id => '1234', :varietal_id => '1515' } but not all of the associations from Wine are belongs_to and thus don't have an entry in wines for _id, making the logic more complicated with joins and what not.
Option 2:
Build a SQL where clause, again using some logic to determine whether to use the id or join against another table... again the logic would be somewhat more complicated, and delving in to SQL makes it feel less rails-y. Or I could be wrong on that front.
Option(s) 3..n:
Things I haven't thought about... your input goes here :)
You could set up a scope in the Wine model to make it more rails-y ...
class Wine < ActiveRecord::Base
belongs_to :region
attr_accessible :name, :region_id
scope :from_region, lambda { |region|
joins(:region).where(:region_id => region.id)
}
end
So then you can do something like:
region = Region.find_by_name('France')
wine = Wine.from_region(region)
Edit 1:
or if you want to be really fancy you could do a scope for multiple regions:
scope :from_regions, lambda { |regions|
joins(:region).where("region_id in (?)", regions.select(:id))
}
regions = Region.where("name in (?)", ['France','Spain']) # or however you want to select them
wines = Wine.from_regions(regions)
Edit 2:
You can also chain scopes and where clauses, if required:
regions = Region.where("name in (?)", ['France','Spain'])
wines = Wine.from_regions(regions).where(:varietal_id => '1515')
Thanks to all who replied. The answers I got would be great for single condition queries but I needed something that could deal with a varying number of conditions.
I ended up implementing my option #1, which was to build a condition hash by iterating through and concatenating _id to the values:
def query_conditions_hash(conditions)
conditions.inject({}) do |hash, (k,v)|
k = (k.to_s + "_id").to_sym
hash[k] = v.to_i
hash
end
end
So that the method would take a hash that was built from params like this:
{ region => '1235', varietal => '1551', product_attribute => '9' }
and drop an _id onto the end of each key and change the value to an integer:
{ region_id => 1235, varietal_id => 1551, product_attribute_id => 9 }
We'll see how sustainable this is, but this is what I went with for now.
There is something i don't quite understand in Rails's belongs_to concept. Documentation states:
Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object
Let's say i have an Employee entity:
class Employee < ActiveRecord::Base
belongs_to :department
belongs_to :city
belongs_to :pay_grade
end
Will the following code fire three updates and if so is there a better way to do it? :
e = Employee.create("John Smith")
Department.find(1) << e
City.find(42) << e
Pay_Grade.find("P-78") << e
You can simply assign it:
e = Employee.new(:name => "John Smith")
e.department = Department.find(1)
e.city = City.find(42)
e.pay_grade = Pay_Grade.where(:name => "P-78")
e.save
I changed the create to new to construct the object before saving it. The constructor takes a hash, not different values. find takes only the id and not a string, use where on a field instead.
You can also use the following:
Employee.create(:name => "John Smith",
:department => Department.find(1),
:city => City.find(42),
:pay_grade => PayGrade.where(:name => "P-78").first
Also note that model names should be camel case: PayGrade instead of Pay_Grade.