rails model mixed of mongodb and mysql - mysql

I have an mysql table called "info_item", which can be extended with a mongodb document:
class CreateInfoItems < ActiveRecord::Migration
def change
create_table :info_items do |t|
t.integer :machine_id
t.binary :external_id
t.integer :info_type_id
t.timestamps null: false
end
end
end
The field "external_id" is going to be an id of a mongodb document.
How can I represent it in my model?
I thought about an object which is inherited from ActiveRecord::Base and includes the Mongoid::Document:
class InfoItem < ActiveRecord::Base
end
class PhoneNumber < InfoItem
include Mongoid::Document
end
Is it going to run? Do you have other ideas?

I think you're better off hooking them together by hand. Both ActiveRecord::Base and Mongoid::Document will try to define all the usual ORM-ish methods so they will be fighting each other; ActiveRecord::Base will also try to do STI with that set up and you don't have any use for STI here.
Don't use binary for the external_id, AR will probably end up trying to serialize the BSON::ObjectId into YAML and you'll end up with a bunch of confusing garbage. Instead, store the BSON::ObjectId as a 24 character string:
t.string :external_id, :limit => 24
and say things like:
info_item.external_id = some_mongoid_document.id.to_s
and manually implement the usual relation methods:
def external
external_id ? WhateverTheMongoidClassIs.find(external_id) : nil
end
def external=(e)
self.external_id = e ? e.id.to_s : nil
end
You might want to override external_id= to stringify inputs as needed and external_id to BSON::Object_id.from_string outputs too.
I work with mixed PostgreSQL and MongoDB apps and this is the approach I use, works fine and keeps everything sensible in both databases.

Related

How to Show Name Instead of Id in Rails

This problem has me completely stumped. I'm still new at RoR and learning.
I have two tables: Countertops and Countmaterial. A user will select all the features for their countertop including the material type. The options for the material are listed in the Countmaterial table and are selection from a collection.
My question is once the selection is made and the Countertop created how do I display the name of the material type on the index page for Countertops instead of the countertype, which is an integer generated to match the name in the Countmaterial table?
I'd rather the index display "Granite" instead of "1", for example. "Granite" is listed in the Countmaterial table and when the user selects "Granite", it populates the Countertop table as "1" in the countertype column. Marble is a "2" and so on...
Here's my schema:
create_table "countertops", force: :cascade do |t|
t.string "size"
t.string "color"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "ZipCode"
t.string "countertype"
end
create_table "countmaterials", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "countertop_id"
end
My Countertop Controller for index:
def index
#countertops = Countertop.all
#countertops = Countertop.includes(:countmaterial).all
end
My index code:
<% #countertops.each do |countertop| %>
<tr>
<td><%= countertop.ZipCode %></td>
<td><%= countertop.countmaterial.name %></td>
Associations:
class Countertop < ActiveRecord::Base
has_one :countmaterial
end
class Countmaterial < ActiveRecord::Base
belongs_to :countertop
end
What do you folks think??
You're going to get confused with your specific model names; when naming models and controllers - keep it SUPER simple. One word...
#app/models/counter.rb
class Counter < ActiveRecord::Base
#columns id | type_id | material_id | size_id | color_id | zip_code| created_at | updated_at
belongs_to :type
belongs_to :material
belongs_to :size
belongs_to :color
delegate :name, to: :size, prefix: true
end
#app/models/option.rb
class Option < ActiveRecord::Base
#columns id | Type | name | created_at | updated_at
has_many :counters
end
#app/models/size.rb
class Size < Option
end
#app/models/type.rb
class Type < Option
end
#app/models/color.rb
class Color < Option
end
#app/models/material.rb
class Material / Option
end
This will give you the ability to do the following:
#config/routes.rb
resources :counters
#app/controllers/counters_controller.rb
class CountersController < ApplicationController
def index
#counters = Counter.all
end
end
#app/views/counters/index.html.erb
<% #counters.each do |counter| %>
<%= counter.size_name %>
<% end %>
To give you context on how this works, you need to know that Rails & Ruby are object orientated. This might not mean a lot, but it's vitally important when developing apps with them.
Object orientated programming is a pattern which puts the object at the center of the code. When you understand how this works, nothing will ever be the same...
In "traditional" programming, you work with user flow. This is known as event driven programming, and although works well for standard apps, it does not suit a Ruby/Rails environment.
Web apps have the capacity to handle so much more data / functionality that it makes perfect sense to treat everything as an object.
Thus, whenever you deal with Ruby, you have to think about everything from the perspective of the objects you're trying to CRUD (Create Read Update Destroy).
This is why your CounterTop model is a little sketchy - what's the object you're trying to invoke?
Once you see that the object sits at the core of how Rails works, you'll be able to construct everything around it, as above.

Ruby on rails database interface

I'm used to MySQL but trying to use Ruby on Rails right now. In MySQL, I would have two tables, with one containing a reference to another ("posts" referring to "topic"). A MySQL query doing what I want would be similar to "SELECT * FROM Posts WHERE posts.topic="topic" ("topic" here is a variable).
However, trying to work with the Ruby model stuff has me confused. The variables being passed between the controller and view are null because they are empty tables.
In my controller:
def topic
#topic = Topic.where(params[:topic])
#posts = Post.where(topic: #topic.object_id)
end
I don't know how to select the posts which have the topic defined by the "topic" variable.
In the view:
<% #posts.each do |post| %>
<p><%= post.title %></p>
<% end %>
The migration files:
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.string :text
t.references :topic
t.timestamps
end
end
end
class CreateTopics < ActiveRecord::Migration
def change
create_table :topics do |t|
t.string :topic
t.timestamps
end
end
end
Given that Post and Topic are related, according to your migrations at least, in the models you should be stating"
class Topic
has_many :posts
and
class Post
belongs_to :topic
Given that you then have an instance of Topic, #topic, you can retrieve all the related records with:
#posts = #topic.posts
I think those methods you put in your controller are fine where they are, but keep in mind that the Rails way is "fat models, skinny controllers." If you put that logic in the model as a method, it's much easier to read in the controller. Also, you should look into scopes, as they'll help you with queries like this down the line too.
In any case, you should stick the following in your Topic model:
scope :by_name, ->(name) { where(topic: name) }
That's essentially the same as doing the following:
def self.by_name(name)
where(topic: name)
end
On your posts model, you'd be able to do the following:
scope :by_topic, ->(topic) { where(topic_id: topic) }
The other problem with what you've stuck in your controller is that when you use scopes, or a "where", it returns an array that contains all of the different records that match your query terms. So, when you call #topic = Topic.where(params[:topic]), you're getting back an array of objects. Therefore, when you do a #topic.id, you're trying to get back the id of an array instead of one object.
Based off of what I showed you before, it makes much more sense for you to do this:
def topic
#topic = Topic.by_name(params[:topic]).first #this returns the first record
#post = Post.by_topic(#topic.id)
end
That will return an array of posts that match the first topic name that you query for.
Alright, first a primer on how database design and how Rails (really, ActiveRecord) works. Basically, you should be connecting posts.topic_id = topic.id, not posts.topic = topic.topic.
Your migration is correct as is, create_table automatically includes an :id PRIMARY KEY column. That said you should know that these are all equivalent:
t.references :topic
t.belongs_to :topic
t.integer :topic_id
In your view, instead of embedding topic.topic and passing that to the controller when the form is submitted, embed topic.id (the documentation for the select helper has a good example of this) and in your controller:
#topic = Topic.find params[:id]
#posts = #topic.posts

Rails virtual attribute with ActiveRecord

I have a MySQL database table with the following simplified schema:
create_table "sensors", force: :cascade do |t|
t.integer "hex_id", limit: 8, null: false
end
where hex_id is declared as a BIGINT in MySQL. I would like for the user to type in a hexadecimal value, then convert it to base 10 and save it in hex_id. To accomplish this, I thought I would create a virtual attribute called hex to store a hexadecimal string of characters. My Sensor model looks like this:
class Sensor < ActiveRecord::Base
attr_accessor :hex
validates :hex, presence: true
before_create :hex_to_bigint
before_update :hex_to_bigint
private
def hex_to_bigint
self.hex_id = hex.to_s(10)
end
end
and the controller is using standard rails-generated code:
def new
#sensor = Sensor.new
end
# POST /sensors
def create
#sensor = Sensor.new(sensor_params)
if #sensor.save
redirect_to #sensor, notice: 'Sensor was successfully created.'
else
render :new
end
end
I created a view with a form that uses the hex attribute.
<%= f.label :hex do %>HEX ID:
<%= f.text_field :hex, required: true, pattern: '^[a-fA-F\d]+$' %>
<% end %>
When I click submit, the params array has the following contents:
{"utf8"=>"✓", "authenticity_token"=>"some_long_token", "sensor"=>{"hex"=>"E000124EB63E0001"}, "commit"=>"Create Sensor"}
My problem is that the attribute hex is always empty, and my validation fails. There are many resources on the web that explain how to use virtual attributes, but very few that explain how to use them in conjunction with ActiveRecord. I have spent hours looking for a way to solve this rather simple problem but have found nothing that works. Any help is appreciated. My ruby version is 2.0.0p481. Thanks!
Please add hex in permitted params. see the code below
private
# Never trust parameters from the scary internet, only allow the white list through.
def sensor_params
params.require(:sensor).permit(:hex_id, :hex)
end
I hope this will help you

How to create automated association in ruby on rails with mysql

i got 2 tables connected with each other.
device and push information are my models.
class Device < ActiveRecord::Base
has_one :pushinformation
end
class Pushinformation < ActiveRecord::Base
belongs_to :device
end
these are my 2 model files and their relationships.
and these are my migration files
class CreateDevices < ActiveRecord::Migration
def change
create_table :devices do |t|
#t.integer :id
t.string "token"
t.string "carrier"
t.string "segment"
#t.datetime :created_at
#t.datetime :updated_at
t.timestamps
end
end
end
class CreatePushinformations < ActiveRecord::Migration
def change
create_table :pushinformations do |t|
t.integer "device_id"
#t.string "token"
t.string "first_name"
t.string "last_name"
t.string "nickname"
t.timestamps
end
end
end
now the thing is , i was able to create a relationship successfully in rails console by saying
device.pushinformation=push
and they were associated.
How can i make this process automated, like when i add one device- it will have a push information table filled aswell,
i thought about having the same attribute and relating them might be the solution. In this case its token and its completely unique.
How can i create this relationships? I need to be able to know which device has what kind of first_name
i m a beginner in ruby and this is a newbie question sorry guys :)
I am not sure I understand completely what you ask but my guess is that you could use a callback on create
class Pushinformation < ActiveRecord::Base
belongs_to :device
after_create :create_push_notification
private
def create_push_notification
...
end
end
check the docs
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
xlembouras's answer is right (to a degree), but as you're new, let me explain it for you:
--
Associations
ActiveRecord associations are nothing magical, they're just a way to associate two "objects" using a relational database setup.
ActiveRecord is an ORM -- "object relationship mapper" -- which basically means it just provides a level of abstraction for your ActiveRecord objects to associate with each other
Herein lies the crux of the matter - you need to apprciate how and why your associations will work, and more importantly, how to populate them correctly:
--
Models
Firstly, you need to appreciate the object-orientated nature of Ruby (& by virtue of running on Ruby, Rails). This is where the Models of Rails play such a vital role -- they allow you to build & manage objects from your database
The ActiveRecord associations give you the ability to manage the associations those objects have - maning if you build one, you should be able to build the other:
#app/models/device.rb
Class Device < ActiveRecord::Base
has_one :push_information
before_create :build_push_information #-> creates associative object before creation
end
#app/models/push_information.rb
Class PushInformation < ActiveRecord::Base
belongs_to :device
end
You need to consider the importance of what I've written above.
What you need is to create a push_information object with the same foreign_key as the device object, which can be achieved by using the build method
This will essentially create a "blank" version of your associative object, saving it with the correct foreign key etc
--
Nesting
Further to this, you have to appreciate the idea of "nesting", especially the method accepts_nested_attributes_for
This allows you to create associative / dependent objects based on your "parent" object:
#app/models/device.rb
Class Device < ActiveRecord::Base
has_one :push_information
accepts_nested_attributes_for :push_information
end
#app/models/push_informtion.rb
Class PushInformation < ActiveRecord::Base
belongs_to :device
end
This gives you the ability to do the following:
#app/controllers/devices_controller.rb
Class DevicesController < ApplicationController
def new
#device = Device.new
#device.build_push_information
end
def create
#device = Device.new(device_params)
#device.save
end
private
def device_params
params.require(:device).permit(:your, :device, :params, push_information_attributes: [:push, :information, :attributes])
end
end
This gives you the ability to populate the devices#new form like so:
#app/views/devices/new.html.erb
<%= form_for #device do |f| %>
<%= f.text_field :your_device_attributes %>
<%= f.fields_for :push_information do |p| %>
<%= p.text_field :your_field %>
<% end %>
<%= f.submit %>
<% end %>
Add a create
method to your Devise class. Something like:
def self.create(token, carrier, segment)
device = Device.new(:token => token, :carrier => carrier, :segment => segment)
pi = Pushinformation.create(device.id,..) # other params
end

Updating a Rails Association table

I'm working on a quiz app in Rails that keeps track of a number of Facts
app/models/fact.rb
class Fact < ActiveRecord::Base
validates(:question, presence: true, uniqueness: { case_sensitive: false })
validates(:answer, presence: true)
end
Every time a user takes a new quiz, they generate an Exam
app/models/exam.rb
class Exam < ActiveRecord::Base
after_create :assign_facts
belongs_to :user
default_scope -> { order('created_at DESC') }
validates :user_id, presence: true
has_many :problems
has_many :facts, through: :problems
def assigned?(fact)
problems.find_by(fact_id: fact.id)
end
def assign!(fact)
problems.create!(fact_id: fact.id)
end
private
def assign_facts
facts = Fact.all.sample(10)
facts.each do |fact|
self.assign!(fact)
end
end
end
Since there are many Exams all using the same Facts, each Exam has_many Facts though an association table of Problems
app/models/problem.rb:
class Problem < ActiveRecord::Base
belongs_to :exam
belongs_to :fact
validates :exam_id, presence: true
validates :fact_id, presence: true
end
excerpt from db/scheme.rb:
create_table "problems", force: true do |t|
t.integer "exam_id"
t.integer "fact_id"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "correct", default: false
end
My problem is that I'm trying to figure out how to store the results of each user's exam (whether they answer a specific question either correctly, or incorrectly). I was planning on updating the association table each time the user answers a question and storing the result in the t.boolean "correct" collumn. This would be a fairly simple matter in PHP/MySQL (UPDATE problems SET correct = 1 WHERE exam = 'id' AND fact = 'id'), but I'm having difficulty figuring out how to do it the Rails way.
Is there some way I can simply, and easily update my associations table (problems) with Rails? OR Should I create a fourth table (maybe 'results' or something) to keep track of the user's correct/incorrect answers? --I know I don't have any controller code here, I'm just thinking out the broad strokes, and I want to keep things simple. Any help would be greatly appreciated.
You are almost there... you have already added a Boolean column called correct in the schema for the problem model, so now you just need to access that as an attribute when updating an Exam. Som somewhere in your controller code, you would say:
ps=#exam_taken.problems
ps.each do |p|
if answered_correctly(p)
p.correct=true
p.save
end
end
# This assumes you have a method that checks correctness inside the binding where the
# above code is written
#exam_taken.save
If you are using Rails3, you would have to also declare the correct attribute as attr_accessible in your model.
And here's a free pro-tip: Default scope is evil :)