I'm trying to do a search on the generated model string, which is formed by has_one associations.
Here my models:
Street.rb
class Street < ActiveRecord::Base
has_many :houses
validates :street_type, presence: true
validates :name, presence: true, uniqueness: {:scope => :street_type, :case_sensitive => false }
def text
street_type + " " + name
end
end
House.rb
class House < ActiveRecord::Base
belongs_to :street
has_many :apartments
validates :street_id, presence: true
validates :number, presence: true
def text
street.text + ", " + number
end
end
Apartment.rb
class Apartment < ActiveRecord::Base
belongs_to :house
has_one :street, through => :house
has_many :accounts
validates :house_id, presence: true
validates :number, presence: true, uniqueness: {:scope => :house_id, :case_sensitive => false}
def text
house.text + ", ap. " + number
end
end
Account.rb
class Account < ActiveRecord::Base
belongs_to :appartment
has_one :house, :through => :appartment
belongs_to :resident
validates :appartment_id, presence: true
validates :resident_id, presence: true, uniqueness: {:scope => :appartment_id, :case_sensitive => false}
def text
uniq_id + " (" + appartment.text + ")"
end
end
I must search all accounts by matching some adress string. And I have no idea how to do it.
Example 1st account text "123-1123-122 (Regent street, 51, ap. 3)"
Example 2nd account text "123-1123-155 (Regent street, 51, ap. 6)"
Example search text "Regent street, 51"
Can somebody help me understand how to do that?
Search against joined fields
You are searching against Account#text, which is nowhere to be found in DB, unless you store it there (see "Search against full Apartment#text"), which (judging by the code) seems not to be the case. You can't use return values of your model methods in ActiveRecord queries. So first you need to parse street type and name, house number and apartment number from your search string.
Assuming you know street type and name, house number and apartment number, you should (minus bugs, I did not actually test that) be able to find the accounts by
Account.
joins(:apartment => { :house => :street }).
where('apartment.number' => apt_num,
'apartment.house.number' => house_num,
'apartment.house.street.name' => street_name,
'apartment.house.street.street_type' => street_type)
Your address information is higly structured. It may or may not be good. Without knowing any details I would suggest you to check if less structured or unstructured address information (plain text field) combined with full text search would work for you.
More on AR queries, see http://guides.rubyonrails.org/active_record_querying.html
Search against full Apartment#text
In case you don't want to parse names and numbers from the generated apartment text field, you need to have it stored in db. You can do it by hooking into before_save and updating a text field (let it be called cached_text) to contain Apartment#text.
class Apartment < ActiveRecord::Base
before_save :update_cached_text
def update_cached_text
self.cached_text = text
end
end
Now of course Apartment#cached_text will be out of sync if you change house number or street name or type. To keep it in sync you need to "resave" (see #touch) all associated apartments each time you save house or street.
Now you can search using full Apartment#text
Account.
joins(:apartment).
where('apartment.cached_text' => text)
or a substring of it
Account.
joins(:apartment).
where("apartment.cached_text LIKE '%?%'", text)
Try with following
Account.includes([:appartment => [:house => :street]]).where("CONCAT('accounts.uniq_id', ' (', 'streets.street_type', ' ', 'streets.name', ' ', 'houses.number', ', ap. ', 'houses.number', ')') like '%?%'", your_search_text)
your_result = Account.includes(:apartment, :apartment => :house, :apartment => {:house => :street}).where(:apartment => {:number => ...}).where(:house => {:number => ...}).where(:street => {:name => ...})
Related
I am using ruby 2.7.1, rails 6.0.0, and the most recent ActiveAdmin. The app is a tech recruiting platform focused on referrals and bounty payments.
I have a BountyPayment model that belongs_to three other models, i.e., here is the app/models/bounty_payment.rb file:
class BountyPayment < ActiveRecord::Base
belongs_to :bounty_status, inverse_of: :bounty_payments
belongs_to :placement, inverse_of: :bounty_payments
belongs_to :user, inverse_of: :bounty_payments
validates :amount_cents, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100000000, message: 'invalid numerical input; accepted range: 0-100000000' }
def to_s
[self.user, self.bounty_amount, self.bounty_status, self.placement].join(' - ')
end
end
bounty_status.rb includes: has_many :bounty_payments, inverse_of: :bounty_status, dependent: :destroy
placement.rb includes: has_many :bounty_payments, inverse_of: :placement, dependent: :destroy
user.rb includes: has_many :bounty_payments, inverse_of: :user, dependent: :destroy
I have many other models and relations in the app and this problem never arises anywhere else.
The unexpected behavior is that when I create a new BountyPayment, including giving it a bounty_status_id, placement_id, and user_id, the Placement column appears blank everywhere in ActiveAdmin. The other columns are not blank. When I download CSV, I can see the correct Placement is there, but I can't see it in ActiveAdmin (if I inspect the element it is blank, i.e., empty string in the HTML). The Placement options are also blank in the dropdown menus to create or edit the BountyPayment in ActiveAdmin. It is as if whatever method used by ActiveAdmin to render the Placements in dropdown menus and pages is returning an empty string or nil value. I don't know why it would do that, and can't replicate it anywhere else.
Below are images of the behavior.
BountyPayment table with empty string for Payment
Details page with empty string
Dropdown not showing string as an option despite showing the correct number of options from the Placement table
Note: I am using a skin on ActiveAdmin.
Here is (I believe) the relevant part of the admin file for BountyPayments (app/admin/bounty_payment.rb):
ActiveAdmin.register BountyPayment do
permit_params do
permitted = :list, :of, :attributes, :on, :model
if current_admin_user && ['create', 'update', 'destroy'].include?(params[:action])
permitted << :id
permitted << :bounty_status_id
permitted << :placement_id
permitted << :user_id
permitted << :amount_cents
end
permitted
end
I believe the Placement resource is also registered correctly in ActiveAdmin in app/admin/placements.rb as shown here:
ActiveAdmin.register Placement do
permit_params do
permitted = :list, :of, :attributes, :on, :model
if current_admin_user && ['create', 'update', 'destroy'].include?(params[:action])
permitted << :id
permitted << :potential_interview_id
permitted << :placement_status_id
permitted << :title
permitted << :notes
permitted << :salary_cents
permitted << :days_since_start
permitted << :at_risk_for_refund
permitted << :full_time_offer
permitted << :remote_position
permitted << :start_date
permitted << :end_date
permitted << :bounty_payments
end
permitted
end
I tested in Chrome, Firefox, and Safari and the behavior was the same in all three browsers.
I ran ActiveAdmin.application.namespaces[:admin].resources.keys in rails console and it shows that all the resources mentioned here are indeed registered so I think the issue is not a missing resource.
Here is the app/models/placement.rb file including the (trivial) to_s method I am using right now:
class Placement < ActiveRecord::Base
belongs_to :potential_interview, inverse_of: :placements
belongs_to :placement_status, inverse_of: :placements
has_many :bounty_payments, inverse_of: :placement, dependent: :destroy
validates :title, length: { in: 1..100, message: 'title length must be between 1 and 100 inclusive' }, allow_blank: true
validates :notes, length: { in: 1..1000, message: 'notes length must be between 1 and 1000 inclusive' }, allow_blank: true
validates :salary_cents, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 1000000000, message: 'invalid numerical input; accepted range: 0-1000000000' }, allow_blank: true
validates :days_since_start, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100000, message: 'invalid numerical input; accepted range: 0-100000' }
def to_s
'hi'
end
end
I am happy to provide additional information. Thanks in advance for any ideas!
ActiveAdmin chooses what to display for an object by picking the first of these methods which the object responds to.
:display_name, :full_name, :name, :username, :login, :title, :email, :to_s
[From Index As Table]
Placement has a title method, so it is using that. Presumably your Placements have a blank title.
You can get around this by defining an explicit display_name method.
I have an model for notifications.
An notification can be posted by an User or an Contact.
and the notification can go to either a business or a notification_area.
people has to be filled, so when there is no user added it has to fill in the Contact that is logged in.
the notification_to has to be filled to so when there is no business added it has to take the latitude and the longitude and add the right area.
I have written the code but it won't work.
First I added it in the controller. But after looking around on google and this site I found I had to add it to the model.
But it still won't work.
What do I do wrong?
I get an error
class Notification < ActiveRecord::Base
belongs_to :people, :polymorphic => true
belongs_to :notification_to, :polymorphic => true
belongs_to :App_of_area
belongs_to :kind
before_validation :if_empty_add_the_other
#validates :photo, presence: true
validates :message, :status, :people_id, :people_type, :notification_to_id, :notification_to_type, :App_of_area_id, :kinds_id, presence: true
def if_empty_add_the_other
unless self.people_type.present?
self.people = current_contact
end
unless self.notification_to_id.present?
if self.longitude && self.latitude
#noa = NotificationArea.where( "latitude_A <= #{self.latitude} AND latitude_B >= #{self.latitude} AND longitude_A <= #{self.longitude} AND longitude_B >= #{self.longitude}")
self.notification_to = #noa.first
end
end
end
end
end
First thing first you should DRY up your code:
belongs_to :business
belongs_to :app_of_area
has_many :people, source_type: "Notification"
has_many :api_keys
before_validation :if_empty_add_the_other
has_secure_password
validates :name, :email, :rights, :password_confirmation, :app_of_area_id, :business_id, presence: true
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
validates :email, uniqueness: true
validates :password, confirmation: true, length: { minimum: 6 }
:as is used for polymorphic associations, I believe you want to use :source_type as shown above. This would allow you to perform self.people << current_contact, but I address this more below.
Why was :App_of_area capitalized? Same with :App_of_area_id?
Your if_empty_add_the_other validation method has a lot wrong with it.
Use unless rather than if not.
Can the two if statements testing latitude and longitude be combined to if self.longitude && self.latitude?
You have to ask yourself, how is current_contact being passed to this function? Also, you're trying to set self.people equal to this phantom current_contact; self.people would contain multiple records, an array if you will, so setting an array equal to an object won't work, hence the self.people << current_contact above.
I am trying to use auto-complete/type-ahead feature provided by following Rails gem https://github.com/maxivak/bootstrap3_autocomplete_input , together with https://github.com/plataformatec/simple_form
Everything works fine in case I choose "new" action for new record in form. I am able to choose value in input field by auto-complete feature. Problem is in case i choose "edit" action to edit already existing record. In that case field does not show correct value (pre-filled by form), but it shows something like: #<Airport:0x007f98b478b7a8>
Even in "show" action, I can see correct value displayed.
I tried to change f.input with f.association as I had it before I started implementing auto-complete, but this did not helped.
Records in Cargo model have correct airports_id reference stored, I checked that manually in rails console.
Question is how can I get correct Airport value pre-filled by form in case I choose "edit" action, instead some kind of reference, I got.
Rails 4.1.7
My code is:
Cargo model:
class Cargo < ActiveRecord::Base
belongs_to :airport
...
Cargo view:
...
<%= f.input :airport, :as => :autocomplete, :source_query => autocomplete_airport_city_airports_url %>
...
Airport model:
class Airport < ActiveRecord::Base
has_many :cargos, :dependent => :destroy
attr_accessible :iata_code, :name, :city
validates :iata_code, :name, :city, presence: true
validates :iata_code, :uniqueness => { :scope => :name }
validates :iata_code, length: { is: 3 }, format: { with: /\A[a-zA-Z\d\s]*\z/ }
validates :name, :city, length: { minimum: 2, maximum: 128 }
def full_airport_name
"#{city} / #{iata_code}"
end
end
Airports controller
class AirportsController < ApplicationController
autocomplete :airport, :city, { :display_value => 'full_airport_name', :full_model=>true }
...
Routes:
resources :airports do
get :autocomplete_airport_city, :on => :collection
end
Actually I found the problem. First of all I refactored Airports model, removed all columns but name, and reseed name column with data concatenated from separate strings IATA code / City. After this, there is need to specify in model, what to show as value. Simply this solved this issue:
class Airport < ActiveRecord::Base
has_many :cargos, :dependent => :destroy
attr_accessible :name
validates :name, presence: true
validates :name, :uniqueness => true
def to_s
name
end
end
This is described, I didnot understand it on first sight previously, in original documentation here https://github.com/maxivak/bootstrap3_autocomplete_input section Model.
User f.association and because rails will automatically look for :nameand you do not have that, you'll have to define it like so:
f.association :airport, label_method: :full_airport_name, value_method: :id........etc
I have two classes
class GpsPoint < ActiveRecord::Base
validates :longitude, :presence => true
validates :latitude, :presence => true
belongs_to :station
end
and
class Station < ActiveRecord::Base
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :gps_point, :presence => true
has_one :gps_point
belongs_to :route
end
The gps points are enter separated from station when a station is created i want to be able select a gps point from a drop down list.
How can i create the dropdown list with all the gps points ?
<%= select('station', 'gps_point_id', GpsPoint.all.collect {|u| [u.name,u.id]}) %>
Check out Formtastic. It's a gem that lets you easily create forms and automatically deals with Foreign Keys really well - lets you create a drop down or radio buttons.
Otherwise there is decent article on rails select helpers:
http://shiningthrough.co.uk/Select-helper-methods-in-Ruby-on-Rails
In my rails app I have 2 models: post and post_translations.
class PostTranslation < ActiveRecord::Base
belongs_to :post
LANGUAGES = %w( en fr es de it )
validates_inclusion_of :language, :in => LANGUAGES
end
class Post < ActiveRecord::Base
has_many :post_translations
end
I want to prevent the same language translation from being submitted twice, so I want to limit the enums to the values not listed in the language column of a particular post_id.
I don't know if I should do this in model, controller or helper.
Which is the best practice?
Thanks in advance.
I'd use an attribute on the class instead of defining it on an instance.
class PostTranslation < ActiveRecord::Base
##languages = %w( en fr es de it )
cattr_reader :languages
belongs_to :post
validates :language, :inclusion => { :in => ##languages },
:uniqueness => { :scope => :post_id }
end
Now to fulfill your requirement of showing only the languages without translations, define a method on Post:
class Post < ActiveRecord::Base
has_many :post_translations
def untranslated
PostTranslation.languages - post_translations.map(&:language)
end
end
Then you can build a select menu by getting a post (#post = Post.find(params[:id]) and populating the collection from #post.untranslated.
It's perfectly valid to keep this in the model. Models should hold primary responsibility for ensuring that the data entered into them is correct.
For your particular case, you can use the :uniqueness validator with a scope passed to it. Basically your validation will ensure that languages are unique in the context of a particular post
The following should work:
validates :language, :inclusion => { :in => LANGUAGES },
:uniqueness => { :scope => :post_id }
If you prefer the Rails 2 style syntax, you can use:
validates_uniqueness_of :language, :scope => :post_id