I'm trying to make a database that keeps track of items for a user.
For example, a user might want to keep track of their cars and the color, make and year of it. They will first create a cars "model", and they can now add as many new cars as they want. When they make a car, they will be prompted to input the color, make and year as well as generic item information.
The user might also want to keep track of their cats. But the cat "model" has different fields than the car "model." Maybe they need to keep track of the weight and breed of their cats. When they want to add an item of type cat to the database, I will prompt them to input the weight and breed NOT color, make and year.
I'm not very experienced with rails or SQL, but I was thinking when a user tells me they want to keep track of cars, I could create a cars table with color make and year. When they input a car, I would store the car as a generic item and also keep a reference to the cars table where I would store the color make and year. Not quite sure how I would implement though, so I'm open to any new ideas. Thanks in advance for your help.
You are quite right that you will need to create a table for both a car and cat, each with their own columns.
Rails comes with Active Record to facilitate the creation of databases. Active record is really easy to use and has some great documentation.
For example, to create a table for a car, you will have to create a car model first.
To get Active Record to create a model, you just use the following command in terminal:
$ rails g model Car color:string make:string year:integer
This model is stored in app/models. Don't forget to migrate your database after you create the model.
In order for a user to enter the properties of the instance, you will need to use form_for. Documentation for a form_for can be found here. To use a form_for you need to declare the instance for the model that you want to change and the method you are going to use once the form has been submitted. You then follow this up with some input fields which are all associated with a property of the instance. The example in the documentation is a pretty good one.
<%= form_for #article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %>
<%= f.text_field :title %>
<%= f.text_area :body, size: "60x12" %>
<%= f.submit "Create" %>
<% end %>
I've been playing with something called Meta models - whereby you have app/models/meta.rb which allows you to invoke models the user defines in the db.
I've had this running, but it's not super efficient.
I'll tell you about it; maybe it will give you some ideas.............
--
Each model can be stripped down to set of 2 common attributes - title & value:
#app/models/node.rb
class Node < ActiveRecord::Base
# columns id | type | title | value | created_at | updated_at
end
This is the very base of the models you wish to use (car / cat etc).
For example, you could make a node have the following stats:
# id | type | title | value | created_at | updated_at
# 1 | Meta::Cat | Greg | A new cat we got | 15/12/2015 | 15/12/2015
# 2 | Meta::Cat | Pip | A little kitten | 16/12/2015 | 16/12/2015
# 3 | Meta::Car | F480 | The red Ferrari | 10/12/2015 | 10/12/2015
This will give you a base set of data which you can then append different associations to.
And yes, the new associations can be stored in the node datatable too.
So in fact, you could have the following:
#app/models/meta.rb
class Meta
classes = %w(cat car)
if classes.any?
classes.each do |klass| #-> "class" is reserved
self.const_set klass.titleize, Class.new(Node) do
belongs_to :user
end
end
end
end
This will programmatically create the Cat and Car classes, which can be called with Meta::Cat etc as required.
I guess this is how you could get your different "base" models.
If you then wanted to add extra associations to that, you'd have to have extra data to make sure that you know which associations each model has.
I've not done this yet, but as you can see above, you can invoke a block which basically allows you to define different things in the pseudo-class:
--
So if you had another meta class called Option, you could use it to set the associations for each other class:
# 2 | Meta::Option | association | has_many :x | 16/12/2015 | 16/12/2015
# 3 | Meta::Option | association | belongs_to :y | 10/12/2015 | 10/12/2015
I still need to think about how it will match the association, but in short, you'll be able to do the following:
#app/models/meta.rb
class Meta
classes = %w(cat car)
if classes.any?
classes.each do |klass| #-> "class" is reserved
self.const_set klass.titleize, Class.new(Node) do
#-> Node.where(name: "association").pluck(:value)
end
end
end
end
This is known as metaprogramming (or it might be pseudoprogramming) and is something which could be done; whether it's efficient is another question entirely.
Related
Im new to ruby on rails I would like to insert an array of checkbox but it only insert the whole array data ["PMT", "MHT"] in one row in mysql. What I want is to create one to many db insert in db.
Currently result
+-------------+------------+
| id | company_name |
+-------------+------------+
| 1 | ["MHT, "PMT"] |
+-------------+------------+
Expected Result
+-------------+------------+
| id | company_name |
+-------------+------------+
| 1 | MHT |
| 2 | PMT |
+-------------+------------+
Controller
def create
ebookAssignedCompanies = EbookCompanyAssigned.create(ebook_company_assigned_param)
ebookAssignedCompanies.save
end
private
def ebook_company_assigned_param
params.permit(company_name:[])
end
View
<% #companies.each do |company| %>
<input type="checkbox" name = "company_name[]" id = "<%= company.id%>" value = "<%= company.company_name %>" class = "checkbox">
<% end %>
Need help for this thanks.
Your code appears to be creating an array in the companies field. You want to change your database setup to reflect your expected result. I presume you want the user to make a selection from a list of companies you have stored in the database. The checkbox dropdown list is created in the view, not in the database. "[]" is an empty array, not a visible checkbox.
First change your database so that each company is on its own row and has its own id. Do a rake db:rollback to get rid of the current table and delete that migration from the db folder in the rails app. Then do rails generate migration Companies name:string to generate a new migration, then rake db:migrate to activate the new table. Then I would suggest you use the simple_form gem and learn from their site how to do a selection from a dropdown list. (after you seed the database with some names). Your confusion involves the word "array." Take it out of your thinking through this particular problem. Yes, you will get an array of companies when you ask for Company.all, but that's a different story for a different time.
I want to show a time link mixing comments and post so I have this objects
#posts = Post::all()
#comments = Comment::all()
If I do this
#post.each ...
... end
#comments.each ...
... end
I will get first posts and after this, the comments. But I want a timeline, how i can create this?
I need to combine both object to create just one ordered list, example:
In post:
id | name | date
1 | post1 | 2015-01-01
2 | post2 | 2013-01-01
In comments:
id | name | date
1 | comment1 | 2014-01-01
2 | comment2 | 2016-01-01
if I do this
post.each ...
comments.each ...
the result will that:
-post1
-post2
-comment1
-comment2
But i need order by date to get
-post2
-comment1
-post1
-comment2
Thanks, and sorry for my ugly english.
Posts and comments are different models (and different tables), so we can't write SQL to get sorted collection, with pagination etc.
Usually I use next approach when I need mixed timeline.
I have TimelineItem model with source_id, source_type and timeline_at fields.
class TimelineItem < ApplicationRecord
belongs_to :source, polymorphic: true
end
Then I add in models logic to create timeline_item instance when needed:
has_many :timeline_items, as: :source
after_create :add_to_timeline
def add_to_timeline
timeline_items.create timeline_at: created_at
end
Then search and output as simple as
TimelineItem.includes(:source).order(:timeline_at).each { |t| pp t.source }
Solution#1
You can achieve this using UNION query.
sql= 'SELECT id, name, date FROM posts UNION ALL SELECT id, name, date FROM comments ORDER BY date'
ActiveRecord::Base.connection.execute(sql)
but union query will only work when you have same column names in both tables.
Solution#2 Add ordering logic in your view.
If you are simply displaying these records on html page then let them load on page without any specific order i.e.(first posts and then comments). Write javascript code to sort DOM elements which will run after page load.
for ref: Jquery - sort DIV's by innerHTML of children
Solution#3 Refactor your DB schema and put posts and comments in same database table. Then you will be able to query on single table.
Something like this,
class Text < ActiveRecord::Base
end
class Post < Text
end
class Comment < Text
end
Query will be Text.order(:date)
Refactoring your DB schema is too much to solve this problem. Do it if it makes sense for your application.
I want to show a time link mixing comments and post so I have this objects
#posts = Post::all()
#comments = Comment::all()
If I do this
#post.each ...
... end
#comments.each ...
... end
I will get first posts and after this, the comments. But I want a timeline, how i can create this?
I need to combine both object to create just one ordered list, example:
In post:
id | name | date
1 | post1 | 2015-01-01
2 | post2 | 2013-01-01
In comments:
id | name | date
1 | comment1 | 2014-01-01
2 | comment2 | 2016-01-01
if I do this
post.each ...
comments.each ...
the result will that:
-post1
-post2
-comment1
-comment2
But i need order by date to get
-post2
-comment1
-post1
-comment2
Thanks, and sorry for my ugly english.
Posts and comments are different models (and different tables), so we can't write SQL to get sorted collection, with pagination etc.
Usually I use next approach when I need mixed timeline.
I have TimelineItem model with source_id, source_type and timeline_at fields.
class TimelineItem < ApplicationRecord
belongs_to :source, polymorphic: true
end
Then I add in models logic to create timeline_item instance when needed:
has_many :timeline_items, as: :source
after_create :add_to_timeline
def add_to_timeline
timeline_items.create timeline_at: created_at
end
Then search and output as simple as
TimelineItem.includes(:source).order(:timeline_at).each { |t| pp t.source }
Solution#1
You can achieve this using UNION query.
sql= 'SELECT id, name, date FROM posts UNION ALL SELECT id, name, date FROM comments ORDER BY date'
ActiveRecord::Base.connection.execute(sql)
but union query will only work when you have same column names in both tables.
Solution#2 Add ordering logic in your view.
If you are simply displaying these records on html page then let them load on page without any specific order i.e.(first posts and then comments). Write javascript code to sort DOM elements which will run after page load.
for ref: Jquery - sort DIV's by innerHTML of children
Solution#3 Refactor your DB schema and put posts and comments in same database table. Then you will be able to query on single table.
Something like this,
class Text < ActiveRecord::Base
end
class Post < Text
end
class Comment < Text
end
Query will be Text.order(:date)
Refactoring your DB schema is too much to solve this problem. Do it if it makes sense for your application.
I have a model which has various different types of information associated with it.
Opportunity.rb
has_many :notes
has_many :emails
has_many :calls
I currently have 3 tables on the show page where in the controller I call
#notes = #opportunity.notes.sorted
#emails = #opportunity.emails.sorted
#calls = #opportunity.calls.sorted
And display them in 3 tables
I'd like to sort them chronologically so I can get a timeline.
Each one of the models has date, and I want to sort by the date then return the objects in date order.
i.e.
The table goes:
Note | Content | 11-Feb | Bob
Call | Content | 07-Feb | Dave
Email | Content | 04-Feb | Steve
Call | Content | 03-Feb | Dave
Note | Content | 01-Feb | Margaret
I can't work out how I merge those tables in the controller into an object I can use in the view so that I can create a table, sort on the date ("created_at") and still keep everything looking nice and clean.
The simplest way would be to combine the 3 collections into an array and sort it by created_at. In your controller:
#combined = (#notes + #emails + #calls).sort_by {|record| record.created_at}
You can then use #combined in your view to show the timeline.
Using ruby on rails, I have a Customer table that I want to be able to add unlimited properties (key value pairs) to. I'm not sure what the key/value pairs will be yet so I'm not sure how to do this. For example, one customer could be:
Customer 1 properties:
color: 'yellow'
brand: 'nike'
sales: '33'
Customer 2 properties:
color: 'red'
phone_number: '1111111111'
purchases: '2'
Basically, customers can have any number of properties in a key/value pair.
How can I do this?
The "traditional" way to do this is with the Entity-Attribute-Value, or EAV pattern. As the name suggests, you'll create a new table with three columns: one for the "entity," which in this case is the Customer, one for the "attribute" name or key, and one for the value. So you'd have a table like this:
customer_properties
+----+-------------+--------------+------------+
| id | customer_id | key | value |
+----+-------------+--------------+------------+
| 1 | 1 | color | yellow |
| 2 | 1 | brand | nike |
| 3 | 1 | sales | 33 |
| 4 | 2 | color | red |
| 5 | 2 | phone_number | 1111111111 |
| 6 | 2 | purchases | 2 |
+----+-------------+--------------+------------+
You'll definitely want an INDEX on key and maybe on value (and customer_id, of course, but Rails will do that for you when you use relation or belongs_to in your migration).
Then in your models:
# customer.rb
class Customer < ActiveRecord::Base
has_many :customer_properties
end
# customer_property.rb
class CustomerProperty < ActiveRecord::Base
belongs_to :customer
end
This enables usage like this:
customer = Customer.joins(:customer_properties)
.includes(:customer_properties)
.where(customer_properties: { key: "brand", value: "nike" })
.first
customer.customer_properties.each_with_object({}) do |prop, hsh|
hsh[prop.key] = prop.val
end
# => { "color" => "yellow",
# "brand" => "nike",
# "sales" => "33" }
customer.customer_properties.create(key: "email", value: "foo#bar.com")
# => #<CustomerProperty id: 7, customer_id: 1, key: "email", ...>
As database design goes this is pretty solid, but as you can see it has some limitations: In particular, it's cumbersome. Also, you're restricted to a single value type (:string/VARCHAR is common). If you go this route you'll likely want to define some convenience methods on Customer to make accessing and updating properties less cumbersome. I'm guessing there are probably gems specifically for making the EAV pattern work nicely with ActiveRecord, but I don't know them off the top of my head and I hope you'll forgive me for not googling, since I'm mobile.
As Brad Werth points out, if you just need to store arbitrary properties and not query by them, serialize is a great alternative, and if you use PostgreSQL even the querying problem is surmountable thanks to its great hstore feature.
Good luck!
You may want to look into the hydra_attribute gem, which is an implementation of the Entity-Attribute-Value (EAV) pattern for ActiveRecord models.
You should be able to use serialize for this, and assign your properties hash to your properties attribute, and retrieve them in the same way.