I have a little class that works with 4 models. I'd like to do things the "right" way. Basically, I want to create 2 Address models, 1 Shipment (w/ 2 address_ids, and 1 Parcel that belongs to Shipment.
At this point, I'm confused. I need to get past this and onto the next big milestone. Does this look promising, or do you recommends saving 2 records in the controller, then use an after_create, or something similar? Thank you.
class Quote
include ActiveModel::Model
attr_accessor address_to: {
:name, :company, :street1, :street2, :street3, :city, :state,
:zip, :country, :phone, :email},
address_from: {
:name, :company, :street1, :street2, :street3, :city, :state,
:zip, :country, :phone, :email
}
def save
return false if invalid?
ActiveRecord::Base.transaction do
user = User.find_by(id: user_id)
user.addresses.create!([{address_from, address_to}]) # how to do this?
end
end
end
If you are using Model association through the tags :belongs_to and :has_many,
you could use accepts_nested_attributes_for :another_child_model for example. It will automatically create or define this association if you permit those params on the controller.
Rails guide for nested attributes
Pay attention on permitting those attributes on the controller.
class YourController < ApplicationController
...
def your_params
params.require(:entity).permit(:etc, :etc, child_model_attributes: [:id, :name, :etc, :etc])
end
...
end
Never use callbacks on models. Hard to test models, unpredictable behavior.
U can use accept_nested_attributes.
If u will have a custom logic with that, u can use Service Objects pattern and put all logic there.
But if u want do it like u do then try to use user.addresses.create!([address_from, address_to])
If you are using Rails 6 or a higher version - there is a method called insert_all to insert multiple records to the database in one INSERT statement.
Check it out on apidock: insert_all
Example:
Book.insert_all([
{ id: 1, title: "Rework", author: "David" },
{ id: 1, title: "Eloquent Ruby", author: "Russ" }
])
(example from the docs)
Related
I want to create a validation that makes sure no other object in a table has the same combination of 3 attributes.
So say my code looks like the below:
class Dog
attr_accessor :color, :name, :height, :weight
end
I want to create a custom rails validation on the Dog class that makes sure there is no other dog in the database that has the same color, name, and weight. Is something like this possible?
Thanks!
You could use validates_uniqueness_of:
validates_uniqueness_of :color, scope: [:name, :weight]
In an app I have 3 types of contact forms - in the model - the attributes :aaa, :bbb, :ccc belongs to the second contact form, the previous attributes belongs to the first contact form.
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :body, :aaa, :bbb, :ccc
validates :name, :email, :body, :aaa, :bbb, :ccc, :presence => true
validates :email, :format => { :with => %r{.+#.+\..+} }, :allow_blank => true
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
What I am trying to do: I am looking for a way, how to validate attributes for the respective contact forms, specifically:
the first contact form contains attributes: :name, :email, :body, which I need to validate
the second contract form contains attributes: :aaa, :bbb, :ccc, :email, which I need to validate
How to do that? How to distinguish, which attributes belongs to which form and validate them?
If there are 3 types of contract forms why not make them 3 separate classes?
If for some erason you still want to keep it in one class, you can do it using 'with_options' magic:
with_options :if => :is_form_1? do |p|
p.validates_presence_of :attr1
p.validates_presence_of :attr2
p.validates_presence_of :attr3
end
with_options :if => :is_form_2? do |p|
p.validates_presence_of :attr4
p.validates_presence_of :attr5
p.validates_presence_of :attr6
end
def is_form_1?
#some logic
end
def is_form_2?
#some logic
end
Still, I don't like the idea of keeping it in one class.
I'd suggest you think about this in behavioural rather than implementation terms. You mention there are three contact forms, but what is the underlying use that you're putting each one to? You shouldn't be thinking about forms when you're setting up your model.
That having been said, you can achieve what you want using the validation_scopes gem. Using validation_scopes, you can define sets of validation rules that you can treat independently. In your controllers, you can then check whichever set of validation rules apply to the context (i.e. which form the user has filled in).
In your model you can set up validation scopes named for each form (or better, named for the context in a way that has semantic value, but I don't know enough about your app to know what the contexts are), like this:
validation_scope :form_one_errors do |vs|
validates :name, :body, :presence => true
end
validation_scope :form_two_errors do |vs|
validates :aaa, :bbb, :ccc, :presence => true
end
Since email needs to be validated in both contexts, you can just set it up as a normal validation (as per your code in the question).
Then in the controller for, say, form one, you can check the scope to see if there are any errors for that context. Note that you have to check the errors for the validation scope separately for the regular validation errors.
if !message.valid?
# Do something with message.errors
elsif message.has_form_one_errors?
# Do something with message.form_one_errors
else
# All good
end
In my models, BookHeader has many Category
So, when edit or create new BookHeader, the form show like this
Enum fix?
I wanna change the "category #{id}" to category name by define a category_enum method but it still don't work. Please help!
Code for BookHeader model
class BookHeader < ActiveRecord::Base
attr_accessible :autho, :category_id, :description, :title, :book_type, :year,:publisher_id,:detail
has_many :books
belongs_to :category
belongs_to :publisher
TYPE = {:ebook=>"Ebook",:paper_book=> "PaperBook",:magazine=> "Magazine",:media=> "Media"}
DEFAULT_TAB = :paper_book
BOOKS_PER_PAGE = 1 # books to show in a pages (pagination)
extend FriendlyId
def book_type_enum #it worked here
TYPE.map{|key, val| [val]}
end
def category_enum #but dont' work here
["a","b"]
end
Code for edit form
edit do
field :title
field :description, :text do
ckeditor do true end
end
field :autho
field :book_type
field :category
end
See the Division attribute in this link
alias_attribute :name, :you_field_you_want_to_display
I think it's more flexible way, there is no need to rename something and everything will work properly
Yeah, I just found the answer, rename a column in your model to "name", it seem to be very magical, but it worked!
I have a Rails app that uses activescaffold and I would like to hide some fields on the update for some of the records.
I have been trying to do it using helper methods, but I can not seem to get that to work.
What is the best way to do this?
The best way to do this is by using one of the security method templates (depending on your need) provided by activescaffold plugin.
Pasted from activescaffold wiki:
Model Methods: Restricting Anything
Else
On your model object you may define
methods (none of which accept any
arguments) in any of four formats,
depending on your need for
granularity.
The formats are:
* #{column_name}_authorized_for_#{crud_type}?
For example if you have an activescaffold based controller called user:
class Admin::UsersController < ApplicationController
active_scaffold do |config|
config.columns = [:username, :name, :email]
end
end
And you only want to allow the user to be able to update the username if they are admin then you can do something like this:
User Model:
class User < ActiveRecord::Base
# ActiveScaffold security template: #{column_name}_authorized_for_#{crud_type}?
def username_authorized_for_update?
# As soon as this method will return false
# the username field will not be available on the update form
return true # Write logic to decide if username field should be visible
end
end
Active Scaffold wiki link: https://github.com/activescaffold/active_scaffold/wiki/Security
If you just want to hide some columns in the update view, then to configure this in the controller is quite easy.
Either you can specify the columns you want to see:
class DocumentsController < ApplicationController
active_scaffold :document do |config|
config.columns = [ :id, :product, :title, :document_type, :author, :organization, :document_approver, :document_location ]
config.list.columns = [ :id, :product, :title, :document_type, :author ]
config.show.columns = [ :product, :title, :document_type, :author, :organization, :document_approver, :document_location ]
config.create.columns = [ :product, :title, :document_type, :document_approver, :document_location ]
config.update.columns = [ :product, :title, :document_type, :organization, :document_approver, :document_location ]
end
end
Or you can exclude the ones you want to hide:
class DocumentsController < ApplicationController
active_scaffold :document do |config|
config.columns = [ :id, :product, :title, :document_type, :author, :organization, :document_approver, :document_location ]
config.list.columns.exclude :organization, :document_approver, :document_location
config.show.columns.exclude :id
config.create.columns.exclude :id, :author, :organization
config.update.columns.exclude :id, :author
end
end
Note that the 'config.columns' is used to define the total number of columns for the controller, and if any of 'list', 'show', 'create' or 'update' are not specifically defined, then 'config.columns' is used per default.
This also means, that if you want the same columns visible for all views except 'update', then you can just define it like:
class DocumentsController < ApplicationController
active_scaffold :document do |config|
config.columns = [ :id, :product, :title, :document_type, :author, :organization, :document_approver, :document_location ]
config.update.columns = [ :product, :title, :document_type, :organization, :document_approver, :document_location ]
end
end
Or:
class DocumentsController < ApplicationController
active_scaffold :document do |config|
config.columns = [ :id, :product, :title, :document_type, :author, :organization, :document_approver, :document_location ]
config.update.columns.exclude :id, :author
end
end
whizcreed's answer is correct, and these ActiveScaffold security model methods are in fact evaluated per-record, so you could do something like this in the model:
def username_authorized_for_update?
return true unless existing_record_check?
return false if userrights != 'admin'
return true
end
where userrights is a string field on this record (admittedly poor example) - but replace this conditional with whatever you want to check on that existing model object.
The security model methods as mentioned in other answers are a good option, however in my case I wanted to allow the field to be shown or hidden depending on the data entered into other columns, by chaining form fields. When using the security model methods, nothing at all is rendered for the field whose security method returned false, which prevents the ActiveScaffold update_column javascript from re-rendering that field when the related column gets updated.
As a simplified example, if my WritingUtensilsController has
config.columns[:type].form_ui = :select
config.columns[:type].options = { options: %w( pencil pen crayon ) }
config.columns[:type].update_columns = [ :ink_color ]
config.columns[:type].send_form_on_update_column = true
And I want the ink_color field to only show up if the type dropdown is set to "pen", using the security method would not work here, because when the type dropdown is changed, we can't locate the ink_color column to update.
Solution
Override the form column partial for the ink_color column (_ink_color_form_column.erb) to conditionally render the normal field, OR a hidden input (with no name attribute but the correct class) inside a dl tag depending on whether or not the writing utensil is a pen:
writing_utensils/_ink_color_form_column.erb:
<% if record.is_pen? %>
<%= form_attribute(column, record, scope, false) %>
<% else %>
<dl><input type="hidden" class="<%= column.name %>-input"></dl>
<% end %>
Calling the form_attribute method will cause the column to be rendered as normal. When the record is not a pen however, the hidden input will allow the javascript to target the parent dl of the input with class ink_color-input for replacement when the type column gets updated by the user.
Bonus:
add an after_render_field method in your controller to manipulate your record when the update_column process happens:
def after_render_field(record, column)
if column.name == :type
if record.type == 'pen'
record.last_sharpened = nil
end
end
end
be sure to add last_sharpened to the update_columns array if you want it to be re-rendered with the new value as well
Can Permalink_fu combine 2 or more model attributes to create a unique permalink?
Let's say I have a Business Model, this model contains :name, :address, :phone, :city, :state, :country etc. attributes.
Right now I have permalink set up in this model only for :name
has_permalink :name
So I would get "/biz/name". However I would like to combine the Business name, city, and a incremental number if there are more than 1 location in the city for that business.
For example I would like to use:
"/biz/joes-coffee-shack-chicago" for the permalink
or if a multple location business
"/biz/starbucks-chicago-92"
Is this possible with the current permalink_fu plugin or some fork of permalink_fu? Or will this require some modification to the permalink_fu plugin?
You can set the attributes as an array:
has_permalink [:one, :two, :three]
They will be automatically joined by -. Permalink_fu also automatically adds a suffix if there's already a record with that permalink.
Add a virtual attribute to your Business model.
class Business < ActiveRecord::Base
attr_accessor :perma_link_attr
has_permalink :perma_link_attr
def perma_link_attr
suffix = 1
[:name, :city, suffix].join("-")
end
end