I am trying to set up a search function in Rails. I have a table called documents. I can search on the subject and the text in the body. Where I am having an issue is trying to to search on fields in associated tables. I have a table called authors. I want to be able to put a name in the author field and return the documents matching that author.
class Search < ActiveRecord::Base
has_many :documents
def search_documents
documents = Document.all
documents = documents.where("subject like ?", "%#{subject}%") if subject.present?
documents = documents.where("body like ?", "%#{text}%") if text.present?
documents = documents.author.where("author like ?", "%#{author}%") if author.present?
return documents
end
end
The subject and body works fine. I've tried all kinds of variations on the author line with no luck . The schema for my documents model looks as follows with author_id linking to the authors table on a has_many relationship between docs and authors
create_table "documents", force: :cascade do |t|
t.string "subject"
t.text "body"
t.integer "category_id"
t.integer "tag_id"
t.integer "author_id"
t.integer "reviewer_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "document_attachment_file_name"
t.string "document_attachment_content_type"
t.integer "document_attachment_file_size"
t.datetime "document_attachment_updated_at"
t.integer "attached_files_id"
t.string "token"
end
Is there a way to reference the author's name in
documents.author.where("author like ?", "%#{author}%") if author.present?
Right now, I am getting an error as follows
Showing C:/Users/cmendla/RubymineProjects/Rl2/app/views/searches/show.html.erb where line #9 raised:
undefined method author' for #<Document::ActiveRecord_Relation:0xa20a0f8>
Rails.root: C:/Users/cmendla/RubymineProjects/Rl2
Application Trace | Framework Trace | Full Trace
app/models/search.rb:12:insearch_documents'
app/views/searches/show.html.erb:9:in `_app_views_searches_show_html_erb__631448441_64839156'
thx
documents.joins(:author).where("authors.name like ?", "%#{author}%") if author.present?
This assumes that the name of the author is stored in a column called name in the authors table and the Document model has a belongs_to :author.
Related
I want to show a text summary for a model in a Rails application.
Currently I'm doing it like this:
class ServiceOrder < ApplicationRecord
has_many :items, class_name: 'ServiceOrderItem',
dependent: :destroy,
inverse_of: :service_order
def link_text
items.left_outer_joins(:product)
.select("string_agg(coalesce(products.description, service_order_items.description), '; ') as description")
.group("service_order_items.service_order_id")
.map(&:description)
.first
end
end
class ServiceOrderItem < ApplicationRecord
belongs_to :service_order, inverse_of: :items
belongs_to :product, optional: true
end
class Product < ApplicationRecord
end
What bothers me is that I'm trying to select a single value, and not a model.
This query does return a "fake" model and extract the value I want, but it's kind of hacky:
Add proper relations I need
items.left_outer_joins(:product)
Add the select value I want
.select("string_agg(coalesce(products.description, service_order_items.description), '; ') as description")
Add group by clause
.group("service_order_items.service_order_id")
Execute the query and extract the description of the "fake" model returned
.map(&:description)
I know this query only returns a single result, but it builds an array with all results, so I extract the single result out of the array
.first
The query I want is this:
select string_agg(coalesce(products.description, service_order_items.description), '; ')
from service_order_items
left outer join products on service_order_items.product_id = products.id
where service_order_items.service_order_id = :id
group by service_order_items.service_order_id;
And this is the query I'm generating, the problem is that the result is enclosed in a model object, then I transform it into an array and then I extract the value I want.
So, how do I tell active record to select a single raw value and not a list of models?
By the way, adding .first before .map doesn't work because it includes an order by in the executed SQL that I can't have (order by service_order_items.id).
The schema:
create_table "products", force: :cascade do |t|
t.integer "organization_id"
t.string "code"
t.string "description"
t.string "brand"
t.string "unit_of_measure"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.decimal "selling_price"
t.index ["organization_id"], name: "index_products_on_organization_id", using: :btree
end
create_table "service_order_items", force: :cascade do |t|
t.integer "service_order_id"
t.decimal "quantity"
t.string "description"
t.integer "product_id"
t.decimal "unit_price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["product_id"], name: "index_service_order_items_on_product_id", using: :btree
t.index ["service_order_id"], name: "index_service_order_items_on_service_order_id", using: :btree
end
create_table "service_orders", force: :cascade do |t|
t.integer "organization_id"
t.text "description"
t.integer "state_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "customer_id"
t.integer "sequential_id"
t.date "start_date"
t.date "end_date"
t.index ["customer_id"], name: "index_service_orders_on_customer_id", using: :btree
t.index ["organization_id"], name: "index_service_orders_on_organization_id", using: :btree
t.index ["state_id"], name: "index_service_orders_on_state_id", using: :btree
end
New answer
The need to use the description on service_order_items if there isn't a product makes this a little tricky. If you want to keep your custom SQL, it should be possible to use pluck with the same text as your select (minus the as description part):
def link_text
items.left_outer_joins(:product)
.group("service_order_items.service_order_id")
.pluck("string_agg(coalesce(products.description, service_order_items.description), '; ')")
.first
end
You also mentioned that you couldn't use first before map because it introduced an undesired order; you could try using take instead of first to avoid that, in which case you wouldn't need pluck.
Note that in either case you're introducing some dependencies on the table names, which could cause problems in more complex queries that require table aliases. If you want to go for less custom SQL,
the most direct way I can think of is to add the following method (probably with a name that better fits your application) to ServiceOrderItem:
def description_for_link_text
product.try(:description) || description
end
Then in ServiceOrder:
def link_text
items.includes(:product).map(&:description_for_link_text).join('; ')
end
The includes(:product) should avoid the N+1 issue where you do one query to get the items and then another query for each product. If you have a page that's displaying this text for multiple service orders, you have to deal with another level of this; often you have to declare a whole bunch of tables in includes even if they're declared in the link_text method.
service_orders = ServiceOrder.some_query_or_scope.includes(items: :product)
service_orders.each { |so| puts so.link_text }
If you do this, I don't think you actually have to have the includes in link_text itself, but if you removed it from there and you called link_text in any other situation, you'd get the N+1 issue again.
Original answer
I'm a bit confused by how your schema fits together: do service_orders and items have a one-to-many relationship, or a many-to-many relationship? How does products relate to items? And I don't have quite enough reputation to comment to ask.
In general, you can use pluck to get an array of values with just the attributes you want. I don't know off the top of my head if it works on virtual attributes, but you may be able to define has_many :through relationships so that you don't need to define string_agg(products.description, '; ') as description to join the strings together. That is, if your ServiceOrder model is able to have a products association like:
has_many :items
has_many :products, through: :items
then you could then just define link_text as products.pluck(:description).join("; "). You may need to play around with your has_many :through definition in order to get it to work right with your schema. Also, doing it this way does mean you have to watch out for potential N+1 query issues; see the Rails guide section on eager loading for how to address that.
I am wanting to import 2 files (County & State). When I import I need to create a relationship with County to State.
My State CSV file has:
id
state e.g. "Connecticut"
abbreviation e.g. "CT"
My County CSV file has:
id
name e.g. "Hartford"
market_state e.g. "Connecticut"
The attributes for MarketCounty are:
create_table "market_counties", force: :cascade do |t|
t.string "name"
t.integer "market_state_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
The attributes for MarketState are:
create_table "market_states", force: :cascade do |t|
t.string "name"
t.string "abbreviation"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
My models have these relationships:
MarketState:
class MarketState < ActiveRecord::Base
has_many :market_reports, as: :location
has_many :market_cities
has_many :market_zips
has_many :market_counties
end
MarketCounty:
class MarketCounty < ActiveRecord::Base
has_many :market_reports, as: :location
has_many :market_cities
has_many :market_zips
belongs_to :market_state
end
My Rake task:
def import_market_states
MarketState.create!(name: "Connecticut", abbreviation: "CT")
end
def import_market_counties
path = Rails.root.join("config/csv/locations/counties.csv")
CSV.foreach(path, headers: true) do |row|
MarketCounty.create! row.to_hash
end
My question is in the the method import_market_counties, row.to_hash does not work.
ActiveRecord::AssociationTypeMismatch: MarketState(#70157557268540)
expected, got String(#70157439770720)
I set it up manually:
MarketCity.create!(name: "Hartford", market_state: MarketState.find_by(abbreviation: "CT")
How can I do a "lookup" in a loop when importing County file (to associate it to the MarketState model)?
To clarify, the issue is that when I am creating the County, I am not trying to populate state with CT. I need to populate state_id with the association.
I found this SO: When importing a CSV, how do I handle data in a row that corresponds to an association? ---
And thinking it may be similar to what I am trying to do - just not sure how to apply it here.
Assuming your MarketState has a name column that is the name of the state:
def import_market_counties
path = Rails.root.join("config/csv/locations/counties.csv")
CSV.foreach(path, headers: true) do |row|
MarketState.find_by_name( row['market_state'].strip )
end
you can also use find_or_create_by if you want to create the state if it doesn't exist.
I have two models connected with a has_and_belongs_to_many association: courses and semesters. rails_admin was only giving me the option to add semesters when creating a course, and not the other way around (and really, it's much more useful to add courses when creating a semester). I made some tweaks the migration:
def change
create_table "courses", force: :cascade do |t|
t.string "department"
t.integer "number"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "semesters", force: :cascade do |t|
t.integer "year"
t.string "season"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "semesters_courses", id: false, force: :cascade do |t|
t.integer "semester_id"
t.integer "course_id"
end
add_index "semesters_courses", ["course_id"], name: "index_semesters_courses_on_course_id"
add_index "semesters_courses", ["semester_id"], name: "index_semesters_courses_on_semester_id"
end
I renamed the intermediary table to semesters_courses from courses_semesters, just for clarity. Not only did this not solve the problem, but now when I try to add a new course, it 500s and tells me:
Could not find table 'courses_semesters'
I know I could make this go away by changing the name back, but I'm not sure where railsadmin is getting that name from (and suspect this to be the source of my problem). I've removed and reinstalled railsadmin, dropped and rewritten the tables, and cleared my browser's cache. When I search my entire project tree for "courses_semesters," I only get results in my error log.
New at Rails dev, so I assume I'm missing some config file somewhere that I need to update, but would love some help on where to find it.
You’re overwriting the join table name.
Option 1 you MUST specify the name of the join table in your models
app/models/course.rb
has_and_belongs_to_many :semesters, join_table: "semesters_courses"
app/models/semester.rb
has_and_belongs_to_many :courses, join_table: "semesters_courses"
Or Option 2 just rename your join table to "courses_semesters" by using migration.
rails g migration rename_courses_semesters
class RenameCoursesSemesters < ActiveRecord::Migration
def self.up
rename_table :semesters_courses, :courses_semesters
end
def self.down
rename_table :courses_semesters, :semesters_courses
end
end
Hope this answers your question.
I understand that the answer may be in similar answers that I've read but I do not yet have the knowledge power to draw a solution from them.
I'm trying to seed a column in a rails model with an array:
["N1", "N2", "N3", "N4", "N5", etc ]
Each value will represent a new line (or entry? not sure of the correct terminology) in the database column.
Currently, similar to what has been suggested in a similar posts, I'm using:
[above array].each do |pc|
Postcodes.create!({ pcode => pc})
end
But I'm getting the following error:
NameError: uninitialized constant Postcodes
I've tried un-pluralising the model name and also un-capitalising but this does not seem to help.
db:schema:
ActiveRecord::Schema.define(version: 20151211095938) do
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "email"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Model:
class Postcode < ActiveRecord::Base
end
Your model is names Postcode, not Postcodes (not the plural s). The following should work:
codes = ["N1", "N2", "N3", "N4", "N5"]
codes.each do |code|
Postcode.create!(pcode: code)
end
I have a Movie model and an Actor model:
class Movie < ActiveRecord::Base
belongs_to :genre
has_many :reviews
has_many :actors
end
class Actor < ActiveRecord::Base
belongs_to :movies
end
These are the attributes for each model:
create_table "actors", force: :cascade do |t|
t.string "name"
t.text "bio"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "movie_id"
end
create_table "movies", force: :cascade do |t|
t.string "title"
t.integer "duration"
t.date "release_date"
t.text "plot"
t.string "director"
t.text "cast"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
When a user fills out the form to create a new movie, I want the input from the 'cast' field to save to the Actor model. What action(s) would I need in my controller and what would I need to do in my form?
I've looked at and tried the following and I'm still stuck:
Rails Updating Data in one Model from another Model's Controller
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
How can I update and save one model from another in Rails?
http://railscasts.com/episodes/196-nested-model-form-part-1
Any help is greatly appreciated, thanks!
You want to create a nested form. You'll need to add accepts_nested_attributes_for :actors to the movie model, then build a subform within the form... Better explanation here:
http://www.theodinproject.com/ruby-on-rails/advanced-forms
Scroll down to "Nested Forms".