I am using algolia gem in my rails app. I am currently combining 3 different models into one index for a combined search of our case studies, blog posts and videos.
I am having trouble as indexing one model will overwrite objects from another model as Algolia appears to use the rails generated ID to map to objectID column.
posts.rb
algoliasearch auto_index: false, if: :open?, index_name: "combined_#{Rails.env}" do
attribute :objectID do
"p#{id}"
end
end
video.rb and case_studies.rb
In the same fashion I try to set attribute :objectID to "v#{id}" for videos as well as set attribute :objectID to "c#{id}" for case studies
This virtual attribute style did not work, so I attempted to create an actual string column on the db and reindex that way to no avail.
If anyone have any ideas here I would really appreciate it and I am running low.
You can use the custom ObjectID inside each of your Model.
Inside your model
def custom_algolia_id
"v#{id}"
end
algoliasearch id: :custom_algolia_id do
attribute :title, :length
end
Related
I have a Rails 4.2 application with mongoid in which I'm importing csv files with test results. I can't define all fields in the model because they change from test to test and theres always around 700 of them. I use Dynamic Attributes and importing and displaying works fine.
I'm trying to use attribute_names method to get all attribute names but all I get is those defined in the model. If I don't define anything in the model it comes back with "_id" only. attributes method on the other hand can see attributes in the actual document on the other hand.
>> #results.first.attributes.count
=> 763
>> #results.first.attribute_names
=> ["_id"]
I also tried fields.keys, same problem
>> #results.first.fields.keys
=> ["_id"]
My model at the moment looks like this
class Result
include Mongoid::Document
include Mongoid::Attributes::Dynamic
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
Result.create! row.to_hash
end
end
end
Can somebody explain how to make it work?
Any help greatly appreciated.
This part is not very clear in the documentation.
and this answer doesn't address how you can make your case works ( I really don't know)... but it has one monkey patch at the end...
all I know is why this case not working...
as the documentation states
When dealing with dynamic attributes the following rules apply:
If the attribute exists in the document, Mongoid will provide you with your standard getter and setter methods.
For example, consider a person who has an attribute of "gender" set on the document:
# Set the person's gender to male.
person[:gender] = "Male"
person.gender = "Male"
# Get the person's gender.
person.gender
this is not your case... cause as it appears you are not defining any attributes in your model...
what applies in your case (from the code you showed and problem you described)
If the attribute does not already exist on the document,
Mongoid will not provide you with the getters and setters and will enforce normal method_missing behavior.
In this case you must use the other provided accessor methods: ([] and []=) or (read_attribute and write_attribute).
# Raise a NoMethodError if value isn't set.
person.gender
person.gender = "Male"
# Retrieve a dynamic field safely.
person[:gender]
person.read_attribute(:gender)
# Write a dynamic field safely.
person[:gender] = "Male"
person.write_attribute(:gender, "Male")
as you can see... there is no way for mongoid to add the setter and getter methods in runtime...
Monkey Patch
you could add a field (maybe string, array, hash, whatever suites you) to the document (attribute exists in the document)
on populating the document from the CSV row.. just save what are the fields of the CSV in that field... (hold the CSV keys in it)
use your predefined field (that holds the keys) instead of using .keys.
code example in your case.
class Result
include Mongoid::Document
include Mongoid::Attributes::Dynamic
field :the_field_that_holds_the_keys, type: Array
# ...
end
and in your controller:
#results.first.some_attribute
#=> method missing error
#results.first[:some_attribute]
#=> some_value
#results.first.the_field_that_holds_the_keys
#=> [:some_attribute, :some_other_attribute, :yada]
I really can't get my head around Rails 4 strong parameters, belongs_to association and form with fields_for.
Imagine I have model for quoting some price:
class Quote < ActiveRecord::Base
belongs_to :fee
accepts_nested_attributes_for :fee
Now, I have seeded some fees into the db, and have put some radiobuttons on my form_for #quote using fields_for. The values of the radiobuttons are simply ids of the records.
Here is the troubling part, the controller:
def create
#quote = Quote.new(quote_params)
...
end
def quote_params
params.require(:quote).permit(:amount_from, fee_attributes: [:id])
end
From my understanding, automagically Rails should fetch fee record with some id, but there is some mystic error instead.
params hash is: "quote"=>{"amount_from"=>"1200", "fee_attributes"=>{"id"=>"1"}}
Log tail:
Completed 404 Not Found in 264ms
ActiveRecord::RecordNotFound (Couldn't find Fee with ID=1 for Quote with ID=)
app/controllers/quotes_controller.rb:14:in `create'
I really don't understand what is going on here, have read Rails association guide, googled for hour for all info, but to no avail.
What I want to achieve here is to understand the correct "Rails way" to fetch some associations for new Quote object using some params I've put in the form.
Guess I got nested_attributes_for wrong, somehow thought it would call Fee.find automagically.
I've opted for ditching fields_for helpers from the form and rendering fields manually like
radio_button_tag 'fee[id]', fee.id
Then in controller I have 2 params methods now:
def quote_params
params.require(:quote).permit(:amount_from)
end
def fee_params
params.require(:fee).permit(:id)
end
And my action looks like
def create
#quote = Quote.new(quote_params)
#quote.fee = Fee.find(fee_params[:id])
...
Any additions on best practices when one has to handle lots of different objects with not so straight init logic are welcome.
I've just added a new column to an existing table in my database:
class AddMoveableDateToDocument < ActiveRecord::Migration
def change
add_column :documents, :moveable_date, :datetime
end
end
In my Rails model I want the moveable_date attribute to be set to a default value upon creation, and the application will be able to change this date later. So, something like:
class Document < ActiveRecord::Base
before_create :set_moveable_date
def set_moveable_date
self.moveable_date ||= self.created_at
end
end
Now, the existing models that are already saved into the database will not have this moveable_date set yet (the value is nil). How do I run through all my existing models and populate the moveable_date attribute with its default value? What is the easiest/best practice way? Can be in the application code itself, in the console, in the terminal, or otherwise. Thanks!
You will get a lot of opinionated answers on this one. Some will suggest the console, some will suggest a one-time rake task.
I would suggest doing it as part of the migration that adds the column. After adding the column, you can run Document.reset_column_information so that the Rails app picks up on your new column, and then iterate through the existing document records and set the moveable date as appropriate.
Or if it's as simple as setting the moveable date to the created_at date, you can use something like Document.update_all("moveable_date = created_at") instead of iterating over them.
That's a good suggestion!
Another way is to add the line before_save :set_moveable_date to your model. It won't accomplish the transition immediately, but if your data is updated on a regular basis, it'd work.
This is somewhat related my question about joins here. By default when I use listing.image.name in my search results view, it does a full query to find the image for every listing in my results array. It even does an extra query just to check if the listing has any images. So to avoid this, I'm adding the following to my Thinking Sphinx query:
#ts_params[:sql][:joins] = "INNER JOIN listing_images ON listing_images.listing_id = listings.id AND listing_images.position = 0"
#ts_params[:sql][:select] = "listings.*, listing_images.image as image_name, listing_images.id as image_id"
This works, however I'm not sure how to generate the full image_url using carrierwave. Previously, where it was doing an extra query per result, I was using listing.image.image_url(:sizename). So, I can find the image name and ID from my join as above, but how to I convert this to a full image url via carrierwave? Is there a built-in method to retrieve the url, that doesn't require an 'image' object?
I tried listing.image_id.image_url(:sizename) but it gave an undefined method error as expected.
From carrierwave's perspective, the answer is obvious:
user.avatar.url
user.avatar.thumbnail.url
Here, user is an instance of a model, and avatar is the field on the model with mount_uploader called on it. In your case this would be something like:
listing_image.image_name.url
listing_image.image_name.thumbnail.url
That probably doesn't work, though, because it looks like you may be loading your listing_image fields into the Listing instead of through includes (or the dreaded N+1 non-eager loads). You may need to resolve your other stackoverflow question before this one will be possible.
Edit:
Per our discussion on your other issue, once you've got a has_one :main_image for just the one ListingImage you want, you're going to use something like #listing.main_image.image_name.url and, if you have a version named "thumbnail", #listing.main_image.image_name.thumbnail.url.
I had the similar issue when I was fetching image using query and wanted to build image url using db field instead of issuing full sql query for each image.
I found out that with couple of field fetched from related image table we can build image which will not run any sql for image if we have three fields id, updated_at and image_name this field should be from the table where image is being saved. It could be from the main table where image is saved as separate column or completely separate table use to specially for image here is a sample code
It can be in your helper or in decorator as per your choice
def logo_url(id, updated_at, name)
return if id.blank? || updated_at.blank? || name.blank?
Company.new(id: id, updated_at: updated_at, logo: name).logo
end
and in view you can call this helper method as
<%= logo_url(company.id, company.updated_at, company.logo).url %>
This way you can have your image with url without executing sql query on each image.
So, I am working on migrating this php site with an existing database which I cannot change over to Rails. There is a table: Quotes with a column named type. Whenever I try and create a model of this and set the type, it tells me the following error:
ActiveRecord::SubclassNotFound (Invalid single-table inheritance type: HOME is not a subclass of Quotes)
I don't understand why it thinks its inheriting because it's not supposed to. My create method looks like this:
quote = Quotes.create(
agent_id: agent.id,
client_id: client.id,
type: 'HOME',
status: 0,
date_created: DateTime.now
)
If I comment out the type, everything works fine. But with the Type it errors.
I resolved this by setting the models inheritance_column to nil. Active Record Models can inherit from a table through the attribute :type, setting the inheritance_column to nil removes that attribute allowing you to have a database column named type
class Quote < ActiveRecord::Base
self.inheritance_column = nil
end
I hate having potential gotchas deep in the code especially in the intial processes like generating a model. Better to just change the reserved word to something else and free yourself up to take advantage of inheritance column later if the need comes up. A cleaner solution is listed here -> rename a database column name using migration
It reads;
Execute $> rails generate migration ChangeColumnName
where, ChangeColumnName is the name of our migration. This can be any name.
Now, edit the generated migration file at db/migrate/_change_column_name.rb
class ChangeColumnName < ActiveRecord::Migration
def change
rename_column :table_name, :old_column, :new_column
end
end
$> rake db:migrate
You will have to edit controller and view files e.g. if the model name is Product then you will likely edit these files
/app/views/products/_form.html.erb
/app/views/products/show.html.erb
/app/controllers/products_controller.erb
/app/views/products/index.html.erb