Search in belongs_to associations - ruby-on-rails

sorry for the beginner's question, but I find it hard to understand:
I have assets, in the model:
class Asset < ActiveRecord::Base
belongs_to :project
belongs_to :image
belongs_to :text
belongs_to :link
belongs_to :video
belongs_to :audio
def self.search(search)
if search
where('title LIKE ?', "%#{search}%")
else
scoped
end
end
end
each asset type has its own table and has_many :assets definition in their model. I'd like to search through the respective asset tables and get items with a specific title (then return them as list of assets). How would it formulate the query? Do I need to use searchlogic?

Is there a chance you describe a bit more what you are trying to achieve? You have an Asset table. It seems this is where you would put the "title" and return all rows where your LIKE search matches.
And instead of listing multiple belongs_to statements, you may want to make this a polymorphic table instead:
class Asset < ActiveRecord::Base
belongs_to :assetable, :polymorphic => true
:
:
end
And then in each of the other models...
class Project < ActiveRecord::Base
has_many :assets, :as => :assetable
end
In this way every asset would have a searchable record in Assets. Use the guides or watch Ryan Bates rails cast on polymorphic associations.
I look forward to further information.
## UPDATE ##
Inside your Asset model, create a class method (e.g. search) like this:
def self.search(params) # these are from your search form
self.where("title like ?", "%#{params[:search]}%"
end
Params[:search] is the params form your form, and the Asset model retrieves all the records which match. Your data set will have the assetable_id and assetable_type (which is the other model). You could display these as links or use this data to retrieve additional info for each model returned, etc.
The key here, as yo will read about and has been explained to me, is to push as much of this type of logic to the model (fat models) and keep your controllers thin.
Hope this helps!

Related

extracting related models from the params hash

I have a many to many polymorphic relationship:
class DocumentTask < ActiveRecord::Base
belongs_to :task
belongs_to :document, polymorphic: true
end
class Task < ActiveRecord::Base
has_many :documents, through: :document_tasks
end
class Contact < ActiveRecord::Base
has_many :tasks, though: :document_tasks, as: :document
end
class Service < ActiveRecord::Base
has_many :tasks, through: :document_tasks, as: :document
end
I have a URL that looks like this that sends information to the server:
/contacts/1/tasks?status_id_is=1&user_id_is=1
But sometimes it can look like this (since there is a polymorphic relationship):
/services/1/tasks?status_id_is=1&user_id_is=1
In my controller, I need to get the object of that polymorphic relationship. Typically, I can do the following:
document_id = params.detect {|name,_| name =~ /(.+)_id/}
#document = document_id[0].gsub("_id","").capitalize.constantize.find(document_id[1])
However, when the url contains a query string, this won't work because status_id_is and user_id_is will match the regular expression. An obvious solution would be to change the regex to /^(.+)_id$/ but who knows how many other use cases may arise.
Is there a nice way to achieve what I want? For example, something in the request object like request.association_parameter or params.association_parameter.
For filtering on a collection--like a search or the params you have to filter tasks--I can suggest an alternative approach which is to use params[:q] instead for filtering.
This is in line with "some" conventions for API filtering, like how ransack (gem) and ElasticSearch use q or query params, which is a wrapper param for scoping.
Equivalently, if following this approach, yours will look like below
/contacts/1/tasks?q[status_id_is]=1&q[user_id_is]=1
In this way, you will now have no problems looping in your params.detect because the status_id_is and user_id_is will be inside params[:q] and therefore is one-level deeper in the hash, which the params.detect loop will already no longer take into account. Thus any MODEL_id params will work for the polymorphic route.

How to retrieve attributes from has_one association without belongs_to?

I have several nested classes that look like so:
class Presentation < ActiveRecord::Base
has_many :campaign_records, :as => :campaignable
def campaign_records_text(joiner)
return '' if self.campaign_records.blank?
self.campaign_records.map {|c| c.to_s}.join(joiner)
end
end
class CampaignRecord < ActiveRecord::Base
belongs_to :campaignable, :polymorphic => true
has_one :campaign
has_one :tier_one
has_one :tier_two
def to_s
"#{campaign.name} - #{tier_one.name} - #{tier_two.name}"
end
end
class Campaign < ActiveRecord::Base
has_many :tier_ones
attr_accessible :name
end
class TierOne < ActiveRecord::Base
has_many :tier_twos
belongs_to :campaign
attr_accessible :name
end
class TierTwo < ActiveRecord::Base
belongs_to :tier_one
attr_accessible :name
end
In summary, a Campaign has many associated TierOnes and every TierOne has it's own set of TierTwos. A Presentation has a set of CampaignRecords which link a Campaign,TierOne, and TierTwo together. Note though that a Campaign does not belong_to a CampaignRecord because many CampaignRecords can refer to it.
So here's the problem: I want to change the CampaignRecord.to_s method to return "campaign.name - tier_one.name - tier_two.name" (like shown above) but doing so results in an error when I try to call some_campaign_record.to_s:
ActionView::Template::Error (Mysql2::Error: Unknown column
'campaigns.campaign_record_id' in 'where clause': SELECT 'campaigns'.*
FROM 'campaigns' WHERE 'campaigns'.'campaign_record_id' = # LIMIT 1)
Where did I go wrong here? I know that rails auto generates a lot of getters and setters for me, but the default to_s method is just the usual so how do I override it in the proper rails way? Does a has_one require a belongs_to or is there a belongs_to_many hiding out there somewhere that I should have used instead?
Any help would be very much appreciated! Thanks in advance!
(Also, I saw that my question is very similar to this unanswered question)
EDIT
I'm seeing a bit of confusion about the model structure here so let me try to explain it differently in a way that will hopefully be clearer.
First off, just to be clear a Campaign is very different from a CampaignRecord.
Think of the Campaign-TierOne-TierTwo relationship like a three layered list:
Campaign 1
TierOne 1.1
TierTwo 1.1.1
TierTwo 1.1.2
...
TierOne 1.2
TierTwo 1.2.1
TierTwo 1.2.2
...
...
Campaign 2
TierOne 2.1
TierTwo 2.1.1
TierTwo 2.1.2
...
TierOne 2.2
TierTwo 2.2.1
TierTwo 2.2.2
...
...
Campaign 3
...
The CampaignRecord model is a representation of a Campaign, TierOne, TierTwo tuple. When its first created, you select a Campaign. Then select a TierOne from that Campaign's set of TierOnes. Then a TierTwo from that TierOne's set of TierTwos. In other words, the CampaignRecord model is a path which traverses a Campaign-TierOne-TierTwo tree.
The set of presentation.campaign_records is the set of valid Campaign-TierOne-TierTwo paths which a user has previously associated with that presentation instance. (A Presentation will have zero or more of these paths associated with it.)
The important bit of functionality is that a Presentation should have a variable size set of Campaign-TierOne-TierTwo linkages. While modifying any given Presentation, I need to be able to modify/add/remove Campaign-TierOne-TierTwo linkages to/from a Presentation. I chose to represent those Campaign-TierOne-TierTwo linkages as CampaignRecords. A Presentation can have a bunch of these CampaignRecords, but no Campaign, TierOne, or TierTwo will ever belong_to a CampaignRecord.
So, my question becomes: Why is my Campaign model throwing a "can't find specified column" error when it was never supposed to be looking for that column in the first place?
#presentations = Presentations.all
#presentations.each do |presentation|
presentation.campaign_records.each do |campaign_record|
print campaign_record.to_s # Campaign model throws error here
end
end
Based on the comments above, one error I see is the following:
class CampaignRecord < ActiveRecord::Base
...
has_one :tier_two
...
end
Take a look at the Rails Guide on Associations.. Campaign is going to expect to see a campaign_record_id in any ActiveRecord model that has a has_something association.
My best bet is that you'll want to add that column into the TierTwo database.
Alternately, if every TierTwo's CampaignRecord can be inferred through its TierOne, you could also hypothetically do:
class CampaignRecord < ActiveRecord::Base
...
has_many :tier_twos, through: :tier_one
...
end
However, that doesn't seem like the right option. You say a CampaignRecord has only one TierTwo, and TierOne can have many TierTwos.
Let me know if this helps. It's possible I'm missing some information about your business logic that would help clarify my recommendation.
Solution seems pretty obvious to me now, in hindsight. My CampaignRecord model had fields to store IDs of other model records. But the has_one in the CampaignRecord implies that other models should be storing the ID of the CampaignRecord. Solution was to change the has_ones to belongs_tos so that the lookup goes in the other direction:
class CampaignRecord < ActiveRecord::Base
belongs_to :campaignable, :polymorphic => true
belongs_to :campaign
belongs_to :tier_one
belongs_to :tier_two
def to_s
"#{campaign.name} - #{tier_one.name} - #{tier_two.name}"
end
end

Rails 4: How to set up associations so that a user has many types of assets?

I'd like to set up my application such that a User has many Assets, and the assets table would reference other tables by it's columns "asset_type" and "asset_id".
Each asset type, should have it's own table with it's own columns.
This should allow: user.assets.count to get a count of all asset types
Also:
user.images to get all the images where id = asset_id (and asset_type = "image").
I've looked at polymorphic associations, multiple table inheritance, has_many :through, etc. I can't seem to get this one figured out.
I tried to upload a diagram, but I do not have enough reputation.
I apologize in advance if this has been asked before, perhaps it's the wording I'm searching or otherwise - but I've been very unsuccessful at finding this solution after several attempts. Any help very much appreciated!
You could do something like this
class User < ActiveRecord::Base
has_many :assets
has_many :images
end
class Asset < ActiveRecord::Base
belongs_to :user
belongs_to :profile, polymorphic: true
end
class Image < Asset
# inherits user, profile and all Asset attributes
end
class ImageProfile < ActiveRecord::Base
has_one :asset, as: :profile, dependent: :destroy
end
So it's STI for Asset which of course needs a "type" column (string). You can then have all the types you need: Image, Video, Document, ...
And polymorphic association for the profiles of each type. ImageProfile is the table for Image specific columns, and you create other tables for VideoProfile, DocumentProfile, etc.
You can read more about this architecture here http://railscasts.com/episodes/394-sti-and-polymorphic-associations
If ImageProfile has a column exif_data, then you access it via image.profile.exif_data.
To create a new image, you can do this:
#image = current_user.images.new.tap do |image|
image.profile = ImageProfile.new profile_params
end

HABTM relationship, How to input multiple values for id?

I have a User model which has_many Portfolios, which has_many Assets which has_and_belongs_to_many AssetHistories.
Basically User 1 might have Google in their portfolio and User 2 might also have Google in their portfolio. Why populate the database with duplicate lines of stock price history for Google when I can have a many-to-many (HABTM) relationship. However what throws me off is what to put for asset_id in the AssetHistory model when it will be multiple values. I.e. it needs to reference both user 1 and user 2. User 1's Google might be asset.id 1 and User 2's Google might be asset.id 2. Therefore how do the entries in the AssetHistory model reference both the ids?
It seems pretty clear that asset_id can't be 2 values simultaneously but I can't wrap my head around this. Am I supposed to use a foreign_key and make Google the key? If so, I still have issues in my Asset model for what entry to put for Asset_History_id, because the asset Google, will have maybe 30 lines of stock price history. Each stock price history would be a different Asset_History_id.
Can someone help explain what I'm doing wrong?
Note that I am using after_save in my asset model to populate the asset price histories. I.e. when someone adds an Asset, it populates the asset_history, but it doesn't populate the asset_history_id field in the Asset model and it doesn't populate the asset_id in the AssetHistory model because I'm at a lost on what to do there.
My asset model has:
class Asset < ActiveRecord::Base
attr_accessible :asset_symbol, :shares, :cost, :date_purchased, :asset_history_id
belongs_to :portfolio
has_and_belongs_to_many :asset_histories
after_save populatepricehistory
private
def populatepricehistory
#uses an api to download price data as an array and imports it to AssetHistory...
#I expect something should go here to fill out the asset_history_id field in the Asset Model
#while simultaneously filling out the asset_id in the AssetHistory model
end
end
Asset History model
class AssetHistory < ActiveRecord::Base
attr_accessible :close, :date, :asset_id, :asset_symbol
has_and_belongs_to_many :assets
end
Migration for AssetHistoryAsset join table
class AssetHistoryAssetJoinTable < ActiveRecord::Migration
def up
create_table :asset_histories_assets, :id=> false do |t|
t.integer :asset_id
t.integer :asset_history_id
end
end
def down
drop_table :asset_histories_assets
end
end
My suggestion would be this:
class User < ActiveRecord::Base
has_many :assets, :through => :porfolios
has_many :porfolios
end
class Porfolio < ActiveRecord::Base
has_many :assets
has_many :users
end
class Asset < ActiveRecord::Base
has_many :users, :through => :portfolios
has_many :portfolios
has_and_belongs_to_many :asset_histories
end
By the way, do you really need a many-to-many relationship between Asset and AssetHistory? I would imagine each instance of AssetHistory to refer to only one Asset, probably by means of belongs_to :asset / has_one :asset_history.

How to create correct database scheme of cooking site?

I want to make a cooking site but don't know the correct was to build database.
My models are: Recipe and Ingredient.
Ingredient in recipe should be autocomplete field. The problem is that user can put there any text. ("cucumber" or "cucamber") and it will be different ingredients.
I want to make a search by ingredients and links to them. What is the best way to do it?
A recipe has many items, which in turn keeps a reference to an ingredient, an amount and a measure type. So you can go with:
rails generate model Recipe name:string description:text
rails generate model Item recipe:references ingredient:references amount:decimal measure:string
rails generate model Ingredient name:string
and then add to your classes:
class Recipe < ActiveRecord::Base
has_many :items
has_many :ingredients, :through => :items
# this allows things like #recipes = Recipe.using("cucumber")
scope :using, lambda do |text|
joins(:ingredients).where("ingredients.name LIKE ?", "%#{text}%")
end
end
class Item < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
VALID_MEASURES = %w[oz kg tbsp] # use for "select" tags in forms
validates :measure, :inclusion => VALID_MEASURES
end
class Ingredient < ActiveRecord::Base
belongs_to :item
end
From here you start building your views, autocomplete, whatever your imagination allows.

Resources