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
Related
I have a Plan model that has_many Versions. I'm nesting Version inputs inside a Plan form, and even though the validations seem to be working, the form inputs don't display any errors.
I don't know if this is relevant, but because the nested inputs are scattered around the form, I open the nested input blocks with simple_fields_for :versions_attributes[0] instead of simple_fields_for :versions because I want to be specific that all the inputs around the form correspond to the same object. Otherwise the hash would be built with a different key for each different block (eg: versions_attributes: { 0: {amount: value}, 1: {another_field: another_value} } instead of versions_attributes: { 0: {amount: value, another_field: another_value}}.
plan.rb:
class Plan < ApplicationRecord
has_many :versions
accepts_nested_attributes_for :versions
validates_associated :versions
end
version.rb
class Version < ApplicationRecord
belongs_to :plan
validates :amount, presence: true
end
plans_controller.rb
class PlansController < ApplicationController
def new
#plan = current_account.plans.build
#version = #plan.versions.build
end
def create
#plan = current_account.plans.build(plan_params)
if #plan.save
redirect_to plan_path(#plan)
else
#plan = #base_plan.plans[0]
render :new
end
end
def plan_params
params[:plan][:versions_attributes]["0"]&.merge!(
account_id: current_account.id,
)
params.require(:plan).permit(
:name,
:short_description,
versions_attributes: %i[id account_id amount],
)
end
end
form.html.erb:
<%= simple_form_for [#plan] do |f| %>
<%= f.input :name %>
<%= f.input :short_description %>
<%= f.simple_fields_for "versions_attributes[0]" do |v| %>
<%= v.input :amount %>
<% end %>
<% end %>
Problem:
#version.errors contains a hash with the object's validation errors. However, the related inputs don't render validation errors nor are the form-group-invalid has-error CSS classes added (provided by the simple_form gem).
My first guess is that it's got something to do with the create action. My second guess is that it's got something to do with the way I'm opening the nested input's blocks (described above).
Either way, I'm confused because #version.errors contains the nested object's errors.
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
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.
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
Environment: Rails 2.3.11 w/ MySQL 5.0
Here is my slideshow model:
class Slideshow < ActiveRecord::Base
validates_presence_of :title, :description
end
Using the console, if I run:
Slideshow.new(:title => "", :description => "").save!
it returns:
Validation failed: Title can't be blank, Description can't be blank
which is correct.
However, when I submit a blank HTML form to the create action:
def create
#slideshow = Slideshow.new(params[:slideshow])
if #slideshow.save
redirect_to(...)
else
render(:action => 'new')
end
end
only the :title field fails validation. I've verified that what is being passed in the params is:
Parameters: {"commit"=>"Submit", "slideshow"=>{"title"=>"", "description"=>""}, "action"=>"create", "controller"=>"manage/slideshows"}
Why is the description field NOT failing validation here?
Thanks.
Try this :
validates_length_of :description
for more details ... http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_length_of
when You submit a blank HTML form to the create action, it should go in else of create action and your form should have this line:
<%= f.error_messages %>
to show you the errors.
This turned out to be a syntax issue. It was occurring on a testing server where there were two models with very similar names (one an updated version of the other). During testing I used the wrong one. My apologies for any unnecessary head-scratching :)