I have a has_many_through relationship between a Contributor and a Resource through a Contributorship. What makes it unusual is that when I set up the association I need to set a contribution_type for the :
model Contributor
has_many contributorships
has_many contributors, through: contributorships
end
model Resource
has_many contributorships
has_many resources, through: contributorships
end
model Contributorships
attr_accessible :contribution_type, :contributor, :archive_resource
belongs_to resources
belongs_to contributors
end
Setting up an association involves either:
c = Contributor.create!()
r = Resource.create!()
c.contributorships.create!(resource:r, contribution_type: :author)
Or (If I don't want to save upfront):
c = Contributor.new()
r = Resource.new()
cs = Contributorship.new(contributor:c, resource:r, contribution_type: :author)
c.save!
r.save!
cs.save!
If I didn't need to set the contribution_type atttribute on the Contributorship join Model I could do:
c.resources << r
So is there a more elgant way of doing this and setting the attribute at the same time?
You can use build to automatically set associations between instantiated objects.
contributor = Contributor.new
contributor.contributorships.build(:contribution_type => :author)
contributor.resources.build
contributor.save!
If the value of contributor_type is dependent on values from one of the other models I'd be inclined to just write a before_validation callback to set the correct corresponding value every time a Contributorships was created. i.e.
model Contributorships
attr_accessible :contribution_type, :contributor, :archive_resource
belongs_to resources
belongs_to contributors
before_validation :set_contributor_type
def set_contributor_type
if self.new_record?
# Whatever code you need to determine the type value
self.contributor_type = value
end
end
end
If instead this is a user defined value then you'll need to use the accepts_nested_attributes_forin which ever model defines your contributor_type and then in the form use fields_for to allow the user to set the value when creating the record. This is covered very comprehensively and in good detail in the following railscast: http://railscasts.com/episodes/197-nested-model-form-part-2?view=asciicast
I.e:
<%= form_for(contributor) do |f| %>
# Whatever fields you want to save for the contributor
<% contributorship = f.object.contributorships.build %>
<%= f.fields_for(:contributorships, contributorship) do |new_contributorship| %>
# Whatever fields you want to save for the contributorship
<% resource = new_contributorship.object.resources.build %>
<%= f.fields_for(:resources, resource) do |new_resource| %>
# Whatever fields you want to save for the resource
<% end %>
<% end %>
<% end %>
Related
The extra costs column in the Reservation model doesn't get calculated and saved on creating a new reservation. It's getting calculated and saved when the reservation is edited (even without changing any values in the form). It seems the checkboxes values are not being received by the calculate method or something.
Reservation has_many :bookings, has_many :extras, :through => :bookings
Booking belongs_to :extra, belongs_to :reservation
Extra has_many :bookings, has_many :reservations, :through => :bookings
before_save :calculate_extras_cost
def calculate_extras_cost
self.extras_cost = self.extras.sum(:daily_rate) * total_days
end
<%=hidden_field_tag "reservation[extra_ids][]", nil %>
<%Extra.all.each do |extra|%>
<%= check_box_tag "reservation[extra_ids][]", extra.id, #reservation.extra_ids.include?(extra.id), id: dom_id(extra)%>
<% end %>
Use the form collection helpers instead of creating the inputs manually:
<%= form_for(#reservation) do |f| %>
<%= f.collection_check_boxes(:extra_ids, Extra.all, :id, :name) %>
<% end %>
Also make sure you are whitelisting the :extra_ids property.
One other thing to bear in mind when using callbacks is that the parent record must be inserted into the database before the child records! That means you cannot use self.extras.sum(:daily_rate) in the callback because it relies on the child records being in the database.
You could use self.extras.map(&:daily_rate).sum to sum the values of the associated models from memory in Ruby. Another option would be to use association callbacks.
I would like to synchronize a has_many association by foreign key. It seems I have to write custom code to do this. Is there any Rails / Active Record magic / Gem to achieve this? Specifically, I'd like to synchronize a join-table where the pairs of foreign keys should be unique.
class Food < ActiveRecord::Base
has_many :food_tags, :dependent=>:destroy, :inverse_of => :food
accepts_nested_attributes_for :food_tags, :allow_destroy => true
end
class FoodTag < ActiveRecord::Base
belongs_to :tag, :inverse_of=>:food_tags
belongs_to :food, :inverse_of=>:food_tags
end
class Tag < ActiveRecord::Base
has_many :food_tags, :dependent=>:destroy, :inverse_of=>:tag
has_many :foods, :through=>:food_tags
end
For my form with nested attributes (or my JSON API), I'd really like to omit the FoodTag id and use the tag_id to synchronize when updating a food.
I want to submit this on update to show that the tag is set or cleared
# this one is set
food[food_tag_attributes][0][tag_id] = 2114
food[food_tag_attributes][0][_destroy] = false
# this one is cleared
food[food_tag_attributes][1][tag_id] = 2116
food[food_tag_attributes][1][_destroy] = true
Instead, I have to submit this for update:
# this one is set
food[food_tag_attributes][0][id] = 109293
food[food_tag_attributes][0][tag_id] = 2114
food[food_tag_attributes][0][_destroy] = false
# this one is cleared
food[food_tag_attributes][0][id] = 109294
food[food_tag_attributes][1][tag_id] = 2116
food[food_tag_attributes][1][_destroy] = true
This pushes a burden to the client to know the IDs of the food tag records instead of just being able to Set or Clear tags based on the tag id.
Can this be done easily? I'm sure I could write a before_save filter on Food, but it seems like there should be a reasonably generic solution.
There is an option called index: for fields_for in the view helper. You can set the index as your foreign_key. Then instead of sequential or some arbitrary numbers, your foreign_key will be used as the key to refer to your object.
EDIT:
<%= form_for #person do |person_form| %>
<%= person_form.text_field :name %>
<% #person.addresses.each do |address| %>
<%= person_form.fields_for address, **index**: address.id do |address_form|%>
<%= address_form.text_field :city %>
<% end %>
<% end %>
<% end %>
What i want to do -
I've got 2 models Record and Author. when calling Record.create params i whant to pass params for associated Author model.
Record has column body and Author has column name
When i try to pass as follows
Record.create { body: "some text", author: { name: 'Some name'}}
i get error ActiveRecord::UnknownAttributeError: unknown attribute: author
How can i do what i need ?
UPDATE 1
association - Record has author
Nested Attributes
You'll probably be looking for accepts_nested_attributes_for, or inverse_of - both relying on an association between your two models:
#app/models/record.rb
Class Record < ActiveRecord::Base
has_one :author
accepts_nested_attributes_for :author
end
#app/models/author.rb
Class Author < ActiveRecord::Base
belongs_to :record
end
Essentially, you'll need to build the associative data, allowing you to send the associated attributes through to your other model. I'll explain this further down the page
This is what I would do if I were you:
#app/controllers/records_controller.rb
Class RecordsController < ApplicationController
def new
#record = Record.new
#record.author.build
end
def create
#record = Record.new record_params
#record.save
end
private
def record_params
params.require(:record).permit(:record, :attributes, author_attributes: [:name])
end
end
#app/views/records/new.html.erb
<%= form_for #record do |f| %>
<%= f.text_field :record %>
<%= f.fields_for :author do |a| %>
<%= a.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>
This will allow you to save the author params / attributes upon save
--
inverse
Inverse attributes are also another idea for you.
I'm not sure whether they'll work directly in this instance, but you could use the following:
#app/models/record.rb
Class Record < ActiveRecord::Base
has_one :author, inverse_of: :author
before_create :build_record
end
#app/models/author.rb
Class Author < ActiveRecord::Base
belongs_to :record, inverse_of: :record
before_create :set_options
private
def set_options
self.draft = true unless self.record.draft.present?
end
end
This means you should be able to access the nested attribute data (I'm not sure whether you have to use accepts_nested_attributes_for still in this instance) in your other model
ActiveRecord Objects
Finally, you need to consider the role of ActiveRecord objects in this setup
Please remember you're not just passing single items of data here - you're constructing & passing objects. This means you have to consider how they work & what they mean. I'll give you a brief explanation:
Rails, because its built on Ruby, is an object-orientated framework. This means that every piece of data you create / use in this is an object. Objects are much different than variables - they are deeper & have much more data contained within them, allowing them to be used in a variety of different ways:
Rails makes use of objects in many different ways; the main one being that a lot of the helpers & other methods build themselves around the objects. That's why you get the resources directive in your routes, and can do the following: <%= link_to #user.name, #user %>
The problem many people have is they don't understand the value of object-orientation in a Rails app, and consequently try and think about their logic from the perspective of a disjointed system. Conversely, and this will help you tremendously, you need to consider that every time you create a record, you're building an object, and consequently, you need to ensure you build your app around them.
As noted, you have to ensure you have an association between the objects you wish to create. If you do that, you'll be able to build them both at the same time
Try this hopefully will solve your problem:
class Record < ActiveRecord::Base
has_one :author
accepts_nested_attributes_for :author, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
end
And for more details see accepts_nested_attributes_for
In my reservations table I have a rooms (text) field to store hash values such (1 => 3) where 1 is roomtype and 3 corresponds to the amount of rooms booked by the same agent.
My Reservation model
serialize reserved_rooms, Hash
Here is my nested resource
resources :hotels do
resources :roomtypes, :reservations
end
RoomType stores a single room type which belongs to Hotel model. Though I can enlist roomtypes within my reservation form I do not know how I can create a dynamic hash via form to create/update this hash.
I have this but I am looking for a way to create a dynamic hash "key, value" set. Meaning, if Hotel model has two RoomType my hash would be {12 = > 5, 15 => 1} (keys corresponds to the roomtype_ids while values are the amount}
<%= f.fields_for ([:roomtypes, #hotel]) do |ff| %>
<% #hotel.roomtypes.each do |roomtype| %>
<%= ff.label roomtype.name %>
<%= f.select :reserved_rooms, ((0..50).map {|i| [i,i] }), :include_blank => "" %>
<% end %>
<% end %>
What I want is what this website has in the availability section (nr. of rooms):
specs: rails 4.1, ruby 2.1
Note: If you think there is a design problem with this approach (storing reserved_room in a serialized field) I can follow another path by creating another table to store the data.
Might need tweaking but i used similar code with check-boxes and it worked!
<% #hotel.roomtypes.each do |roomtype| %>
<%= f.label roomtype.name %>
<%= f.select :"reserved_rooms[roomtype.id]", ((0..50).map {|i| [i,i] }), :include_blank => "" %>
<% end %>
This gets messy enough that I would probably consider going with a separate models as you mentioned. I would simply do:
class Hotel < ActiveRecord::Base
has_many :room_types
has_many :rooms, :through => :room_types
end
class RoomType < ActiveRecord::Base
has_many :rooms
end
class Room < ActiveRecord::Base
has_many :reservations
belongs_to :room_type
end
class Reservation < ActiveRecord::Base
belongs_to :room
belongs_to :agent
end
class Agent < ActiveRecord::Base
has_many :reservations
end
Then just use a generic form to submit the # Rooms integer, and let your controller handle making multiple reservations...? Maybe I'm not understanding your objective well enough...
Rails 4 has a new feature called Store you would love. You can easily use it to store a hash set which is not predefined. You can define an accessor for it and it is recommended you declare the database column used for the serialized store as a text, so there's plenty of room. The original example:
class User < ActiveRecord::Base
store :settings, accessors: [ :color, :homepage ], coder: JSON
end
u = User.new(color: 'black', homepage: '37signals.com')
u.color # Accessor stored attribute
u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
# There is no difference between strings and symbols for accessing custom attributes
u.settings[:country] # => 'Denmark'
u.settings['country'] # => 'Denmark'
I want to display has_many relationship columns in ransackable attributes list. So that I can display them in the dropdown.
I have a member model
class Member < ActiveRecord::Base
has_many :memberships
def self.ransackable_attributes(auth_object = nil)
if auth_object == 'admin'
super
else
super & ['first_name', 'last_name', 'license_number', 'memberships_membership_number_cont']
end
end
And membership model has some columns like membership_number which is unique and a string. Now in the dropdown of members listing page I want to provide membership_number, so that user can select membership_number from the dropdown and enter a value to search the respective member.
Any suggestions?
The dropdown I am taking about is:
PS: In the screenshot you may be looking for a dropdown for contains all/contain any ie options dropdown. I made is just one only contains any. Thats why its not visible.
You need to define the ransackable_attributes method in associated model for custom searchable attributes of that model. So your Membership model should be something like:
class Membership < ActiveRecord::Base
belongs_to :member
...
def self.ransackable_attributes(auth_object = nil)
['membership_number', ...]
end
end
And specify associations in ranssack form like:
<%= f.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.attribute_select associations: [:memberships] %>
<% end %>
<% end %>