How would I go about doing this? Is it advisable?
I have a table called "Item" with columns "name" and "price".
Price changes all the time and I want to store all the changes so I can graph them.
I was thinking "price" should be an array? Is this correct?
Thanks
I would use a separate table to track the price history. Something simple like this:
create_table :item_prices do |t|
t.integer :item_id, :null => false
t.decimal :price, :null => false, :precision => 7, :scale => 2
t.timestamps
end
Note that the price is a decimal, not a float. Never ever use floating point for money.
Then in Item, something like this:
class Item
has_many :item_prices
before_save :update_price_history, :if => :price_changed?
private
def update_price_history
self.item_prices.create!(:price => self.price)
end
end
A nice advantage of this is that you track when the price changed as well as the price itself, that might make your graph look a bit more sensible.
Related
I created two tables in my app (Rails 3):
def change
create_table :articles do |t|
t.string :name
t.text :content
t.timestamps
end
create_table :tags do |t|
t.string :name
t.timestamps
end
create_table :articles_tags do |t|
t.belongs_to :article
t.belongs_to :tag
end
add_index :articles_tags, :article_id
add_index :articles_tags, :tag_id
end
I want to be able to search for articles based on tags in two ways:
Articles with ANY of the given tags (union)
Articles with ALL of the given tags (intersection)
So, in other words, something that allows me to do this this:
tag1 = Tag.create(name: 'tag1')
tag2 = Tag.create(name: 'tag2')
a = Article.create; a.tags << tag1
b = Article.create; b.tags += [tag1, tag2]
Article.tagged_with_any(['tag1', 'tag2'])
# => [a,b]
Article.tagged_with_all(['tag1', 'tag2'])
# => [b]
The first one was relatively easy. I just made this scope on Article:
scope :tagged_with_any, lambda { |tag_names|
joins(:tags).where('tags.name IN (?)', tag_names)
}
The problem is the second. I have no idea how to do this, in ActiveRecord or SQL.
I figure that I might be able to do something icky like this:
scope :tagged_with_all, lambda { |tag_names|
new_scope = self
# Want to allow for single string query args
Array(tag_names).each do |name|
new_scope = new_scope.tagged_with_any(name)
end
new_scope
}
but I'm betting that's crazy inefficient, and it just smells. Any ideas about how to do this correctly?
As you said, that scope is crazy inefficient (and ugly).
Try with something like this:
def self.tagged_with_all(tags)
joins(:tags).where('tags.name IN (?)', tags).group('article_id').having('count(*)=?', tags.count).select('article_id')
end
The key is in the having clause. You may also want to have a look at the SQL division operation between tables.
I have a Model called Status, its handling a table with two columns Stat and Colour.
Since these columns are also Model methods I would expect the following to work without an error
#a = Status.where(:stat => "Operational")
#a.colour = "Green"
However when I call #a.colour I receive an error stating that the method 'colour=' does not exist.
I am calling #a.colour from within seeds. This is just a model, it does not have a controller with it.
What am i doing wrong?
--Edit--
Model
class Status < ActiveRecord::Base
end
schema
create_table "statuses", :force => true do |t|
t.string "stat"
t.string "colour"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
Is this what you requested? I did not fully understand the request,
Kind Regards
I suppose Status.where() returns more than one record. So you are trying to call the color= method on an array which obviously does not exist!
So you need to iterate trough all found records, using
Status.where(:stat => "Operational").each do |a|
a.colour = "Green"
end
For more information check the Rails ActiveRcord Query Interface guide, it tells you:
If you’d like to add conditions to your find, you could just specify them in there, just like Client.where("orders_count = '2'"). This will find all clients where the orders_count field’s value is 2.
I am modifying a Documents table from using three columns (article1, article2, and article3) to one (articles) which has a string of comma-separated IDs stored in it (i.e., 23,4,33,2). That's all working well, but I'm trying to adjust the functions that read the three columns to read the one and I'm getting rather stuck.
In the model I have:
scope :all_articles, lambda {|p| where(:page => p) }
In the controller I have this:
#articles = (1..3).to_a.map { |i| Article.all_articles(i).reverse }
And in the view:
<% #articles.each_with_index do |a, i| %>
<%= a[i].name %>
<% end %>
It's just a bit beyond me at this point.
Cheers!
It's usually not good practice to put store the ids in a column like you have done. It is better to break that relationship out into a Has and Belongs to Many relationship. You set it up in your models like this:
class Document < ActiveRecord::Base
#...
has_and_belongs_to_many :articles
#...
end
class Article < ActiveRecord::Base
#...
has_and_belongs_to_many :documents
end
Then you will create a join table that ActiveRecord will use to store the relationships.
create_table :articles_documents, :id => false do |t|
t.integer :article_id
t.integer :document_id
end
add_index :articles_documents, [:article_id, :document_id], unique: true
This will allow you to query a lot more efficiently than you are currently doing. For example, to find all documents that have some article id. You would do:
#documents = Document.joins(:articles).where("articles.id = ?", some_article_id)
Or if you want to query for a document and return the articles with it:
#documents = Document.includes(:articles).where(some_conditions)
In a Rails(3.2) app, I have a class method on a Model like this:
def import(level, max = 10)
db = ActiveRecord::Base.connection
result = db.execute("SELECT word FROM levels WHERE level == #{level} AND word NOT IN (SELECT entry FROM words) limit #{max};");
It just imports 10 new words(create 10 records) at a time that do not exist as Word record yet.
The schema looks something like this:
create_table "levels", :force => true do |t|
t.string "word"
t.integer "level"
end
create_table "words", :force => true do |t|
t.string "entry"
t.integer "level", :default => 0
t.text "definition"
t.string "thesaurus", :default => "none"
end
I'm an SQL noob. Messing with rails dbconsole(sqlite3, I'm using sqlite3 on a server as well), I somehow came up with the raw sql query above. I sort of know that I can do the same thing with Arel. How am I supposed to construct the query with ActiveRecord?
The following (untested) should work. It uses pluck in the subquery.
Level.where(:level => level).where("word NOT IN (?)", Word.pluck(:entry)).limit(max)
#Gazler's solution looks like it works, but I'll provide an alternative using MetaWhere syntax which is a bit more concise:
Level.where(:level => level, :word.not_in => Word.pluck(:entry)).limit(max)
How do you order by decimal or date fields?
class CreateUserPrices < ActiveRecord::Migration
def self.up
create_table :user_prices do |t|
t.decimal :price, :precision => 8, :scale => 2
t.date :purchase_date
t.timestamps
end
end
end
I'm trying to sort my user prices to be ordered by price and purchase date.
searchable do
text :product_name do
product.name
end
# field for :price?
# field for :purchase_date?
end
What goes in the model?
Make sure when you add any new code to the searchable do block you rake sunspot:reindex.
decimal fields use float and date fields use time
searchable do
text :product_name do
product.name
end
time :purchase_date
float :price
end